diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 0000000000..9c267144ce --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,150 @@ +{ + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": false, + "contributors": [ + { + "login": "postspectacular", + "name": "Karsten Schmidt", + "avatar_url": "https://avatars1.githubusercontent.com/u/52302?v=4", + "profile": "http://thi.ng", + "contributions": [ + "code", + "doc", + "maintenance" + ] + }, + { + "login": "nkint", + "name": "Alberto", + "avatar_url": "https://avatars3.githubusercontent.com/u/609314?v=4", + "profile": "https://github.com/nkint", + "contributions": [ + "code", + "example", + "bug" + ] + }, + { + "login": "acarabott", + "name": "Arthur Carabott", + "avatar_url": "https://avatars1.githubusercontent.com/u/66132?v=4", + "profile": "http://www.arthurcarabott.com/", + "contributions": [ + "code", + "ideas", + "example", + "blog" + ] + }, + { + "login": "andrew8er", + "name": "AndrΓ© Wachter", + "avatar_url": "https://avatars1.githubusercontent.com/u/179225?v=4", + "profile": "http://andrewachter.de", + "contributions": [ + "code", + "ideas", + "bug" + ] + }, + { + "login": "gavinpc-mindgrub", + "name": "Gavin Cannizzaro", + "avatar_url": "https://avatars1.githubusercontent.com/u/29873545?v=4", + "profile": "https://github.com/gavinpc-mindgrub", + "contributions": [ + "code", + "bug", + "ideas" + ] + }, + { + "login": "loganpowell", + "name": "Logan Powell", + "avatar_url": "https://avatars1.githubusercontent.com/u/3408191?v=4", + "profile": "https://github.com/loganpowell", + "contributions": [ + "doc", + "bug", + "ideas" + ] + }, + { + "login": "vorg", + "name": "Marcin Ignac", + "avatar_url": "https://avatars2.githubusercontent.com/u/171001?v=4", + "profile": "http://marcinignac.com", + "contributions": [ + "bug" + ] + }, + { + "login": "arcticnoah", + "name": "arcticnoah", + "avatar_url": "https://avatars2.githubusercontent.com/u/7544636?v=4", + "profile": "https://github.com/arcticnoah", + "contributions": [ + "code" + ] + }, + { + "login": "allforabit", + "name": "allforabit", + "avatar_url": "https://avatars3.githubusercontent.com/u/537189?v=4", + "profile": "https://github.com/allforabit", + "contributions": [ + "bug", + "code", + "ideas" + ] + }, + { + "login": "IvanWoo", + "name": "Yifan Wu", + "avatar_url": "https://avatars2.githubusercontent.com/u/15613549?v=4", + "profile": "https://yifanwu.studio/", + "contributions": [ + "bug", + "doc" + ] + }, + { + "login": "stwind", + "name": "stwind", + "avatar_url": "https://avatars0.githubusercontent.com/u/250297?v=4", + "profile": "https://pngupngu.com/", + "contributions": [ + "code", + "bug" + ] + }, + { + "login": "evilive3000", + "name": "evilive", + "avatar_url": "https://avatars1.githubusercontent.com/u/5011293?v=4", + "profile": "https://github.com/evilive3000", + "contributions": [ + "code" + ] + }, + { + "login": "Bnaya", + "name": "Bnaya Peretz", + "avatar_url": "https://avatars0.githubusercontent.com/u/1304862?v=4", + "profile": "https://github.com/Bnaya", + "contributions": [ + "code", + "bug", + "ideas" + ] + } + ], + "contributorsPerLine": 7, + "projectName": "umbrella", + "projectOwner": "thi-ng", + "repoType": "github", + "repoHost": "https://github.com" +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..d35aa01471 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text=auto eol=lf +*.gif -text +*.jpg -text +*.mp4 -text +*.pdf -text +*.png -text +*.gz -text diff --git a/.gitignore b/.gitignore index 1d7c0b3e5e..e9bed07f49 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,10 @@ *.js *.log *.map +*.o *.tgz +*.wasm +*.wat *.zip /.gtm/ build diff --git a/.prettierrc b/.prettierrc index 776b96e715..2bbeb7c420 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,5 @@ { "tabWidth": 4, - "arrowParens": "always" -} + "arrowParens": "always", + "endOfLine": "lf" +} \ No newline at end of file diff --git a/README.md b/README.md index 6f844f6a58..247b337740 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,38 @@ # @thi.ng/umbrella [![Travis status](https://api.travis-ci.org/thi-ng/umbrella.svg?branch=master)](https://travis-ci.org/thi-ng/umbrella) +[![Code Climate](https://api.codeclimate.com/v1/badges/592940419adb5bf8abaf/maintainability)](https://codeclimate.com/github/thi-ng/umbrella/maintainability) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org/) [![Discord chat](https://img.shields.io/discord/445761008837984256.svg)](https://discord.gg/JhYcmBw) [![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?label=%40thing_umbrella&style=social)](https://twitter.com/thing_umbrella) ## About -Mono-repository for almost 100 thi.ng TypeScript/ES6 projects, a wide +> "A collection of functional programming libraries that can be composed together. +> Unlike a framework, thi.ng is a suite of instruments and you (the user) must be +> the composer of. Geared towards versatility, not any specific type of music." +> — [@loganpowell](https://twitter.com/logantpowell/status/1186334119812304901) via Twitter + +Mono-repository for 100+ thi.ng TypeScript/ES6 projects, a wide collection of largely data transformation oriented packages and building blocks for: - Functional programming (composition, memoization, transducers, multi-methods) +- ES6 iterators - Stream based, reactive programming, dataflow graphs / pipelines -- Data structures & data transformations for wide range of use cases +- WebWorker workflow abstractions +- Data structures & data transformations for wide range of use cases (list, sets, maps) +- Value-based equivalence +- FSM primitives, parser generators - Data driven UI components, event & side effect handling -- Immutable data handling -- Geometry generation, processing & visualization -- Vector & matrix implementations with optional support for strided layouts +- Canvas-based Immediate mode GUI components +- Immutable data handling, state containers, transacted state updates, Undo-Redo history +- 2D geometry generation, processing, conversion & visualization +- Vector & matrix implementations with optional support for strided layouts, SIMD etc. +- Color space conversions, cosine gradients +- Multi-format pixel buffers, conversions, Porter-Duff alpha-blending operators - Declarative WebGL 1/2 abstraction layer +- S-expression parser & runtime infrastructure for custom DSL creation - DSL for shader functions defined in TypeScript and cross-compilation to GLSL, JS, VEX etc. - Low-level tooling for binary data, shared memory / WASM / WebGL interop - etc. (see package overview below) @@ -49,7 +63,7 @@ Most packages: ## Examples -There's a steadily growing number (~60) of standalone examples +There's a steadily growing number (~70) of standalone examples (different complexities, often combining functionality from several packages) in the [examples](./examples) directory. @@ -73,6 +87,12 @@ contribute, please first read [this document](./CONTRIBUTING.md). ## Projects +### New / unreleased packages in development + +(These packages are still unreleased and only available on their feature or `develop` branches) + +- [@thi.ng/scenegraph](https://github.com/thi-ng/umbrella/tree/feature/scenegraph/packages/scenegraph) - 2D/3D scenegraph + ### Fundamentals | Project | Version | Changelog | Description | @@ -84,6 +104,7 @@ contribute, please first read [this document](./CONTRIBUTING.md). | [`@thi.ng/compose`](./packages/compose) | [![version](https://img.shields.io/npm/v/@thi.ng/compose.svg)](https://www.npmjs.com/package/@thi.ng/compose) | [changelog](./packages/compose/CHANGELOG.md) | Functional composition helpers | | [`@thi.ng/defmulti`](./packages/defmulti) | [![version](https://img.shields.io/npm/v/@thi.ng/defmulti.svg)](https://www.npmjs.com/package/@thi.ng/defmulti) | [changelog](./packages/defmulti/CHANGELOG.md) | Dynamic multiple dispatch | | [`@thi.ng/dsp`](./packages/dsp) | [![version](https://img.shields.io/npm/v/@thi.ng/dsp.svg)](https://www.npmjs.com/package/@thi.ng/dsp) | [changelog](./packages/dsp/CHANGELOG.md) | DSP utils, oscillators | +| [`@thi.ng/ecs`](./packages/ecs) | [![version](https://img.shields.io/npm/v/@thi.ng/ecs.svg)](https://www.npmjs.com/package/@thi.ng/ecs) | [changelog](./packages/ecs/CHANGELOG.md) | Entity-Component System | | [`@thi.ng/equiv`](./packages/equiv) | [![version](https://img.shields.io/npm/v/@thi.ng/equiv.svg)](https://www.npmjs.com/package/@thi.ng/equiv) | [changelog](./packages/equiv/CHANGELOG.md) | Deep value equivalence checking | | [`@thi.ng/errors`](./packages/errors) | [![version](https://img.shields.io/npm/v/@thi.ng/errors.svg)](https://www.npmjs.com/package/@thi.ng/errors) | [changelog](./packages/errors/CHANGELOG.md) | Custom error types | | [`@thi.ng/math`](./packages/math) | [![version](https://img.shields.io/npm/v/@thi.ng/math.svg)](https://www.npmjs.com/package/@thi.ng/math) | [changelog](./packages/math/CHANGELOG.md) | Assorted common math functions & utilities | @@ -98,6 +119,7 @@ contribute, please first read [this document](./CONTRIBUTING.md). |---------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------|----------------------------------| | [`@thi.ng/csp`](./packages/csp) | [![version](https://img.shields.io/npm/v/@thi.ng/csp.svg)](https://www.npmjs.com/package/@thi.ng/csp) | [changelog](./packages/csp/CHANGELOG.md) | Channel based async ops | | [`@thi.ng/fsm`](./packages/fsm) | [![version](https://img.shields.io/npm/v/@thi.ng/fsm.svg)](https://www.npmjs.com/package/@thi.ng/fsm) | [changelog](./packages/fsm/CHANGELOG.md) | FSM / parser primitives | +| [`@thi.ng/grid-iterators`](./packages/grid-iterators) | [![version](https://img.shields.io/npm/v/@thi.ng/grid-iterators.svg)](https://www.npmjs.com/package/@thi.ng/grid-iterators) | [changelog](./packages/grid-iterators/CHANGELOG.md) | 2D grid iterator strategies | | [`@thi.ng/iterators`](./packages/iterators) | [![version](https://img.shields.io/npm/v/@thi.ng/iterators.svg)](https://www.npmjs.com/package/@thi.ng/iterators) | [changelog](./packages/iterators/CHANGELOG.md) | ES6 generators / iterators | | [`@thi.ng/sax`](./packages/sax) | [![version](https://img.shields.io/npm/v/@thi.ng/sax.svg)](https://www.npmjs.com/package/@thi.ng/sax) | [changelog](./packages/sax/CHANGELOG.md) | SAX-like XML parser / transducer | | [`@thi.ng/transducers`](./packages/transducers) | [![version](https://img.shields.io/npm/v/@thi.ng/transducers.svg)](https://www.npmjs.com/package/@thi.ng/transducers) | [changelog](./packages/transducers/CHANGELOG.md) | Composable data transformations | @@ -151,37 +173,39 @@ contribute, please first read [this document](./CONTRIBUTING.md). | [`@thi.ng/hiccup-css`](./packages/hiccup-css) | [![version](https://img.shields.io/npm/v/@thi.ng/hiccup-css.svg)](https://www.npmjs.com/package/@thi.ng/hiccup-css) | [changelog](./packages/hiccup-css/CHANGELOG.md) | CSS from nested JS data structures | | [`@thi.ng/hiccup-markdown`](./packages/hiccup-markdown) | [![version](https://img.shields.io/npm/v/@thi.ng/hiccup-markdown.svg)](https://www.npmjs.com/package/@thi.ng/hiccup-markdown) | [changelog](./packages/hiccup-markdown/CHANGELOG.md) | Hiccup-to-Markdown serialization | | [`@thi.ng/hiccup-svg`](./packages/hiccup-svg) | [![version](https://img.shields.io/npm/v/@thi.ng/hiccup-svg.svg)](https://www.npmjs.com/package/@thi.ng/hiccup-svg) | [changelog](./packages/hiccup-svg/CHANGELOG.md) | hiccup based SVG vocab | +| [`@thi.ng/imgui`](./packages/imgui) | [![version](https://img.shields.io/npm/v/@thi.ng/imgui.svg)](https://www.npmjs.com/package/@thi.ng/imgui) | [changelog](./packages/imgui/CHANGELOG.md) | Immediate mode GUI | | [`@thi.ng/interceptors`](./packages/interceptors) | [![version](https://img.shields.io/npm/v/@thi.ng/interceptors.svg)](https://www.npmjs.com/package/@thi.ng/interceptors) | [changelog](./packages/interceptors/CHANGELOG.md) | Composable event handlers & processor | | [`@thi.ng/router`](./packages/router) | [![version](https://img.shields.io/npm/v/@thi.ng/router.svg)](https://www.npmjs.com/package/@thi.ng/router) | [changelog](./packages/router/CHANGELOG.md) | Customizable browser & non-browser router | ### Geometry & visualization -| Project | Version | Changelog | Description | -|---------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------|-------------------------------------| -| [`@thi.ng/color`](./packages/color) | [![version](https://img.shields.io/npm/v/@thi.ng/color.svg)](https://www.npmjs.com/package/@thi.ng/color) | [changelog](./packages/color/CHANGELOG.md) | Color conversions, gradients | -| [`@thi.ng/dot`](./packages/dot) | [![version](https://img.shields.io/npm/v/@thi.ng/dot.svg)](https://www.npmjs.com/package/@thi.ng/dot) | [changelog](./packages/dot/CHANGELOG.md) | Graphviz DOM & export | -| [`@thi.ng/geom`](./packages/geom) | [![version](https://img.shields.io/npm/v/@thi.ng/geom.svg)](https://www.npmjs.com/package/@thi.ng/geom) | [changelog](./packages/geom/CHANGELOG.md) | 2D only geometry types & ops | -| [`@thi.ng/geom-accel`](./packages/geom-accel) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-accel.svg)](https://www.npmjs.com/package/@thi.ng/geom-accel) | [changelog](./packages/geom-accel/CHANGELOG.md) | Spatial indexing data structures | -| [`@thi.ng/geom-api`](./packages/geom-api) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-api.svg)](https://www.npmjs.com/package/@thi.ng/geom-api) | [changelog](./packages/geom-api/CHANGELOG.md) | Shared types & interfaces | -| [`@thi.ng/geom-arc`](./packages/geom-arc) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-arc.svg)](https://www.npmjs.com/package/@thi.ng/geom-arc) | [changelog](./packages/geom-arc/CHANGELOG.md) | 2D elliptic arc utils | -| [`@thi.ng/geom-clip`](./packages/geom-clip) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-clip.svg)](https://www.npmjs.com/package/@thi.ng/geom-clip) | [changelog](./packages/geom-clip/CHANGELOG.md) | 2D convex line/shape clipping | -| [`@thi.ng/geom-closest-point`](./packages/geom-closest-point) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-closest-point.svg)](https://www.npmjs.com/package/@thi.ng/geom-closest-point) | [changelog](./packages/geom-closest-point/CHANGELOG.md) | Closest point helpers | -| [`@thi.ng/geom-hull`](./packages/geom-hull) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-hull.svg)](https://www.npmjs.com/package/@thi.ng/geom-hull) | [changelog](./packages/geom-hull/CHANGELOG.md) | 2D convex hull (Graham scan) | -| [`@thi.ng/geom-isec`](./packages/geom-isec) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-isec.svg)](https://www.npmjs.com/package/@thi.ng/geom-isec) | [changelog](./packages/geom-isec/CHANGELOG.md) | Point & shape intersection tests | -| [`@thi.ng/geom-isoline`](./packages/geom-isoline) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-isoline.svg)](https://www.npmjs.com/package/@thi.ng/geom-isoline) | [changelog](./packages/geom-isoline/CHANGELOG.md) | 2D contour line extraction | -| [`@thi.ng/geom-poly-utils`](./packages/geom-poly-utils) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-poly-utils.svg)](https://www.npmjs.com/package/@thi.ng/geom-poly-utils) | [changelog](./packages/geom-poly-utils/CHANGELOG.md) | 2D polygon helpers | -| [`@thi.ng/geom-resample`](./packages/geom-resample) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-resample.svg)](https://www.npmjs.com/package/@thi.ng/geom-resample) | [changelog](./packages/geom-resample/CHANGELOG.md) | nD polyline / curve resampling | -| [`@thi.ng/geom-splines`](./packages/geom-splines) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-splines.svg)](https://www.npmjs.com/package/@thi.ng/geom-splines) | [changelog](./packages/geom-splines/CHANGELOG.md) | nD cubic / quadratic spline ops | -| [`@thi.ng/geom-subdiv-curve`](./packages/geom-subdiv-curve) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-subdiv-curve.svg)](https://www.npmjs.com/package/@thi.ng/geom-subdiv-curve) | [changelog](./packages/geom-subdiv-curve/CHANGELOG.md) | nD iterative subdivision curves | -| [`@thi.ng/geom-tessellate`](./packages/geom-tessellate) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-tessellate.svg)](https://www.npmjs.com/package/@thi.ng/geom-tessellate) | [changelog](./packages/geom-tessellate/CHANGELOG.md) | nD convex polygon tessellators | -| [`@thi.ng/geom-voronoi`](./packages/geom-voronoi) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-voronoi.svg)](https://www.npmjs.com/package/@thi.ng/geom-voronoi) | [changelog](./packages/geom-voronoi/CHANGELOG.md) | 2D iterative delaunay/voronoi | -| [`@thi.ng/iges`](./packages/iges) | [![version](https://img.shields.io/npm/v/@thi.ng/iges.svg)](https://www.npmjs.com/package/@thi.ng/iges) | [changelog](./packages/iges/CHANGELOG.md) | IGES format geometry serialization | -| [`@thi.ng/lsys`](./packages/lsys) | [![version](https://img.shields.io/npm/v/@thi.ng/lsys.svg)](https://www.npmjs.com/package/@thi.ng/lsys) | [changelog](./packages/lsys/CHANGELOG.md) | Extensible L-System architecture | -| [`@thi.ng/matrices`](./packages/matrices) | [![version](https://img.shields.io/npm/v/@thi.ng/matrices.svg)](https://www.npmjs.com/package/@thi.ng/matrices) | [changelog](./packages/matrices/CHANGELOG.md) | Matrix operations | -| [`@thi.ng/pixel`](./packages/pixel) | [![version](https://img.shields.io/npm/v/@thi.ng/pixel.svg)](https://www.npmjs.com/package/@thi.ng/pixel) | [changelog](./packages/pixel/CHANGELOG.md) | Multi-format pixel buffers | -| [`@thi.ng/poisson`](./packages/poisson) | [![version](https://img.shields.io/npm/v/@thi.ng/poisson.svg)](https://www.npmjs.com/package/@thi.ng/poisson) | [changelog](./packages/poisson/CHANGELOG.md) | nD Poisson disk sampling | -| [`@thi.ng/porter-duff`](./packages/porter-duff) | [![version](https://img.shields.io/npm/v/@thi.ng/porter-duff.svg)](https://www.npmjs.com/package/@thi.ng/porter-duff) | [changelog](./packages/porter-duff/CHANGELOG.md) | Alpha blending / compositing ops | -| [`@thi.ng/vectors`](./packages/vectors) | [![version](https://img.shields.io/npm/v/@thi.ng/vectors.svg)](https://www.npmjs.com/package/@thi.ng/vectors) | [changelog](./packages/vectors/CHANGELOG.md) | Fixed & arbitrary-length vector ops | +| Project | Version | Changelog | Description | +|---------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------|------------------------------------------| +| [`@thi.ng/color`](./packages/color) | [![version](https://img.shields.io/npm/v/@thi.ng/color.svg)](https://www.npmjs.com/package/@thi.ng/color) | [changelog](./packages/color/CHANGELOG.md) | Color conversions, gradients | +| [`@thi.ng/dot`](./packages/dot) | [![version](https://img.shields.io/npm/v/@thi.ng/dot.svg)](https://www.npmjs.com/package/@thi.ng/dot) | [changelog](./packages/dot/CHANGELOG.md) | Graphviz DOM & export | +| [`@thi.ng/geom`](./packages/geom) | [![version](https://img.shields.io/npm/v/@thi.ng/geom.svg)](https://www.npmjs.com/package/@thi.ng/geom) | [changelog](./packages/geom/CHANGELOG.md) | 2D only geometry types & ops | +| [`@thi.ng/geom-accel`](./packages/geom-accel) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-accel.svg)](https://www.npmjs.com/package/@thi.ng/geom-accel) | [changelog](./packages/geom-accel/CHANGELOG.md) | Spatial indexing data structures | +| [`@thi.ng/geom-api`](./packages/geom-api) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-api.svg)](https://www.npmjs.com/package/@thi.ng/geom-api) | [changelog](./packages/geom-api/CHANGELOG.md) | Shared types & interfaces | +| [`@thi.ng/geom-arc`](./packages/geom-arc) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-arc.svg)](https://www.npmjs.com/package/@thi.ng/geom-arc) | [changelog](./packages/geom-arc/CHANGELOG.md) | 2D elliptic arc utils | +| [`@thi.ng/geom-clip`](./packages/geom-clip) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-clip.svg)](https://www.npmjs.com/package/@thi.ng/geom-clip) | [changelog](./packages/geom-clip/CHANGELOG.md) | 2D convex line/shape clipping | +| [`@thi.ng/geom-closest-point`](./packages/geom-closest-point) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-closest-point.svg)](https://www.npmjs.com/package/@thi.ng/geom-closest-point) | [changelog](./packages/geom-closest-point/CHANGELOG.md) | Closest point helpers | +| [`@thi.ng/geom-hull`](./packages/geom-hull) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-hull.svg)](https://www.npmjs.com/package/@thi.ng/geom-hull) | [changelog](./packages/geom-hull/CHANGELOG.md) | 2D convex hull (Graham scan) | +| [`@thi.ng/geom-isec`](./packages/geom-isec) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-isec.svg)](https://www.npmjs.com/package/@thi.ng/geom-isec) | [changelog](./packages/geom-isec/CHANGELOG.md) | Point & shape intersection tests | +| [`@thi.ng/geom-isoline`](./packages/geom-isoline) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-isoline.svg)](https://www.npmjs.com/package/@thi.ng/geom-isoline) | [changelog](./packages/geom-isoline/CHANGELOG.md) | 2D contour line extraction | +| [`@thi.ng/geom-poly-utils`](./packages/geom-poly-utils) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-poly-utils.svg)](https://www.npmjs.com/package/@thi.ng/geom-poly-utils) | [changelog](./packages/geom-poly-utils/CHANGELOG.md) | 2D polygon helpers | +| [`@thi.ng/geom-resample`](./packages/geom-resample) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-resample.svg)](https://www.npmjs.com/package/@thi.ng/geom-resample) | [changelog](./packages/geom-resample/CHANGELOG.md) | nD polyline / curve resampling | +| [`@thi.ng/geom-splines`](./packages/geom-splines) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-splines.svg)](https://www.npmjs.com/package/@thi.ng/geom-splines) | [changelog](./packages/geom-splines/CHANGELOG.md) | nD cubic / quadratic spline ops | +| [`@thi.ng/geom-subdiv-curve`](./packages/geom-subdiv-curve) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-subdiv-curve.svg)](https://www.npmjs.com/package/@thi.ng/geom-subdiv-curve) | [changelog](./packages/geom-subdiv-curve/CHANGELOG.md) | nD iterative subdivision curves | +| [`@thi.ng/geom-tessellate`](./packages/geom-tessellate) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-tessellate.svg)](https://www.npmjs.com/package/@thi.ng/geom-tessellate) | [changelog](./packages/geom-tessellate/CHANGELOG.md) | nD convex polygon tessellators | +| [`@thi.ng/geom-voronoi`](./packages/geom-voronoi) | [![version](https://img.shields.io/npm/v/@thi.ng/geom-voronoi.svg)](https://www.npmjs.com/package/@thi.ng/geom-voronoi) | [changelog](./packages/geom-voronoi/CHANGELOG.md) | 2D iterative delaunay/voronoi | +| [`@thi.ng/iges`](./packages/iges) | [![version](https://img.shields.io/npm/v/@thi.ng/iges.svg)](https://www.npmjs.com/package/@thi.ng/iges) | [changelog](./packages/iges/CHANGELOG.md) | IGES format geometry serialization | +| [`@thi.ng/lsys`](./packages/lsys) | [![version](https://img.shields.io/npm/v/@thi.ng/lsys.svg)](https://www.npmjs.com/package/@thi.ng/lsys) | [changelog](./packages/lsys/CHANGELOG.md) | Extensible L-System architecture | +| [`@thi.ng/matrices`](./packages/matrices) | [![version](https://img.shields.io/npm/v/@thi.ng/matrices.svg)](https://www.npmjs.com/package/@thi.ng/matrices) | [changelog](./packages/matrices/CHANGELOG.md) | Matrix operations | +| [`@thi.ng/pixel`](./packages/pixel) | [![version](https://img.shields.io/npm/v/@thi.ng/pixel.svg)](https://www.npmjs.com/package/@thi.ng/pixel) | [changelog](./packages/pixel/CHANGELOG.md) | Multi-format pixel buffers | +| [`@thi.ng/poisson`](./packages/poisson) | [![version](https://img.shields.io/npm/v/@thi.ng/poisson.svg)](https://www.npmjs.com/package/@thi.ng/poisson) | [changelog](./packages/poisson/CHANGELOG.md) | nD Poisson disk sampling | +| [`@thi.ng/porter-duff`](./packages/porter-duff) | [![version](https://img.shields.io/npm/v/@thi.ng/porter-duff.svg)](https://www.npmjs.com/package/@thi.ng/porter-duff) | [changelog](./packages/porter-duff/CHANGELOG.md) | Alpha blending / compositing ops | +| [`@thi.ng/simd`](./packages/simd) | [![version](https://img.shields.io/npm/v/@thi.ng/simd.svg)](https://www.npmjs.com/package/@thi.ng/simd) | [changelog](./packages/simd/CHANGELOG.md) | WebAssembly SIMD vector batch processing | +| [`@thi.ng/vectors`](./packages/vectors) | [![version](https://img.shields.io/npm/v/@thi.ng/vectors.svg)](https://www.npmjs.com/package/@thi.ng/vectors) | [changelog](./packages/vectors/CHANGELOG.md) | Fixed & arbitrary-length vector ops | ### WebGL / GPGPU @@ -193,22 +217,24 @@ contribute, please first read [this document](./CONTRIBUTING.md). | [`@thi.ng/shader-ast-stdlib`](./packages/shader-ast-stdlib) | [![version](https://img.shields.io/npm/v/@thi.ng/shader-ast-stdlib.svg)](https://www.npmjs.com/package/@thi.ng/shader-ast-stdlib) | [changelog](./packages/shader-ast-stdlib/CHANGELOG.md) | 100+ useful AST shader functions | | [`@thi.ng/webgl`](./packages/webgl) | [![version](https://img.shields.io/npm/v/@thi.ng/webgl.svg)](https://www.npmjs.com/package/@thi.ng/webgl) | [changelog](./packages/webgl/CHANGELOG.md) | WebGL 1/2 / GPGPU facilities | | [`@thi.ng/webgl-msdf`](./packages/webgl-msdf) | [![version](https://img.shields.io/npm/v/@thi.ng/webgl-msdf.svg)](https://www.npmjs.com/package/@thi.ng/webgl-msdf) | [changelog](./packages/webgl-msdf/CHANGELOG.md) | MSDF font rendering | +| [`@thi.ng/webgl-shadertoy`](./packages/webgl-shadertoy) | [![version](https://img.shields.io/npm/v/@thi.ng/webgl-shadertoy.svg)](https://www.npmjs.com/package/@thi.ng/webgl-shadertoy) | [changelog](./packages/webgl-shadertoy/CHANGELOG.md) | Shadertoy-like WebGL setup | ### Low-level, binary, memory management -| Project | Version | Changelog | Description | -|---------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------|--------------------------------------------| -| [`@thi.ng/bencode`](./packages/bencode) | [![version](https://img.shields.io/npm/v/@thi.ng/bencode.svg)](https://www.npmjs.com/package/@thi.ng/bencode) | [changelog](./packages/bencode/CHANGELOG.md) | Bencode binary format encoding | -| [`@thi.ng/binary`](./packages/binary) | [![version](https://img.shields.io/npm/v/@thi.ng/binary.svg)](https://www.npmjs.com/package/@thi.ng/binary) | [changelog](./packages/binary/CHANGELOG.md) | Assorted binary / bitwise ops, utilities | -| [`@thi.ng/bitstream`](./packages/bitstream) | [![version](https://img.shields.io/npm/v/@thi.ng/bitstream.svg)](https://www.npmjs.com/package/@thi.ng/bitstream) | [changelog](./packages/bitstream/CHANGELOG.md) | Bitwise input / output streams | -| [`@thi.ng/dlogic`](./packages/dlogic) | [![version](https://img.shields.io/npm/v/@thi.ng/dlogic.svg)](https://www.npmjs.com/package/@thi.ng/dlogic) | [changelog](./packages/dlogic/CHANGELOG.md) | Digital logic ops / constructs | -| [`@thi.ng/leb128`](./packages/leb128) | [![version](https://img.shields.io/npm/v/@thi.ng/leb128.svg)](https://www.npmjs.com/package/@thi.ng/leb128) | [changelog](./packages/leb128/CHANGELOG.md) | WASM based LEB128 varint encoder / decoder | -| [`@thi.ng/malloc`](./packages/malloc) | [![version](https://img.shields.io/npm/v/@thi.ng/malloc.svg)](https://www.npmjs.com/package/@thi.ng/malloc) | [changelog](./packages/malloc/CHANGELOG.md) | Raw & typed array memory pool & allocator | -| [`@thi.ng/morton`](./packages/morton) | [![version](https://img.shields.io/npm/v/@thi.ng/morton.svg)](https://www.npmjs.com/package/@thi.ng/morton) | [changelog](./packages/morton/CHANGELOG.md) | Z-order-curve / Morton coding | -| [`@thi.ng/range-coder`](./packages/range-coder) | [![version](https://img.shields.io/npm/v/@thi.ng/range-coder.svg)](https://www.npmjs.com/package/@thi.ng/range-coder) | [changelog](./packages/range-coder/CHANGELOG.md) | Binary data Range encoder / decoder | -| [`@thi.ng/rle-pack`](./packages/rle-pack) | [![version](https://img.shields.io/npm/v/@thi.ng/rle-pack.svg)](https://www.npmjs.com/package/@thi.ng/rle-pack) | [changelog](./packages/rle-pack/CHANGELOG.md) | Run-length encoding data compression | -| [`@thi.ng/unionstruct`](./packages/unionstruct) | [![version](https://img.shields.io/npm/v/@thi.ng/unionstruct.svg)](https://www.npmjs.com/package/@thi.ng/unionstruct) | [changelog](./packages/unionstruct/CHANGELOG.md) | Wrapper for C-like structs / unions | -| [`@thi.ng/vector-pools`](./packages/vector-pools) | [![version](https://img.shields.io/npm/v/@thi.ng/vector-pools.svg)](https://www.npmjs.com/package/@thi.ng/vector-pools) | [changelog](./packages/vector-pools/CHANGELOG.md) | Data structures for memory mapped vectors | +| Project | Version | Changelog | Description | +|---------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------|-----------------------------------------------| +| [`@thi.ng/bencode`](./packages/bencode) | [![version](https://img.shields.io/npm/v/@thi.ng/bencode.svg)](https://www.npmjs.com/package/@thi.ng/bencode) | [changelog](./packages/bencode/CHANGELOG.md) | Bencode binary format encoding | +| [`@thi.ng/binary`](./packages/binary) | [![version](https://img.shields.io/npm/v/@thi.ng/binary.svg)](https://www.npmjs.com/package/@thi.ng/binary) | [changelog](./packages/binary/CHANGELOG.md) | Assorted binary / bitwise ops, utilities | +| [`@thi.ng/bitstream`](./packages/bitstream) | [![version](https://img.shields.io/npm/v/@thi.ng/bitstream.svg)](https://www.npmjs.com/package/@thi.ng/bitstream) | [changelog](./packages/bitstream/CHANGELOG.md) | Bitwise input / output streams | +| [`@thi.ng/dlogic`](./packages/dlogic) | [![version](https://img.shields.io/npm/v/@thi.ng/dlogic.svg)](https://www.npmjs.com/package/@thi.ng/dlogic) | [changelog](./packages/dlogic/CHANGELOG.md) | Digital logic ops / constructs | +| [`@thi.ng/leb128`](./packages/leb128) | [![version](https://img.shields.io/npm/v/@thi.ng/leb128.svg)](https://www.npmjs.com/package/@thi.ng/leb128) | [changelog](./packages/leb128/CHANGELOG.md) | WASM based LEB128 varint encoder / decoder | +| [`@thi.ng/malloc`](./packages/malloc) | [![version](https://img.shields.io/npm/v/@thi.ng/malloc.svg)](https://www.npmjs.com/package/@thi.ng/malloc) | [changelog](./packages/malloc/CHANGELOG.md) | Raw & typed array memory pool & allocator | +| [`@thi.ng/morton`](./packages/morton) | [![version](https://img.shields.io/npm/v/@thi.ng/morton.svg)](https://www.npmjs.com/package/@thi.ng/morton) | [changelog](./packages/morton/CHANGELOG.md) | Z-order-curve / Morton coding | +| [`@thi.ng/range-coder`](./packages/range-coder) | [![version](https://img.shields.io/npm/v/@thi.ng/range-coder.svg)](https://www.npmjs.com/package/@thi.ng/range-coder) | [changelog](./packages/range-coder/CHANGELOG.md) | Binary data Range encoder / decoder | +| [`@thi.ng/rle-pack`](./packages/rle-pack) | [![version](https://img.shields.io/npm/v/@thi.ng/rle-pack.svg)](https://www.npmjs.com/package/@thi.ng/rle-pack) | [changelog](./packages/rle-pack/CHANGELOG.md) | Run-length encoding data compression | +| [`@thi.ng/soa`](./packages/soa) | [![version](https://img.shields.io/npm/v/@thi.ng/soa.svg)](https://www.npmjs.com/package/@thi.ng/soa) | [changelog](./packages/soa/CHANGELOG.md) | Memory mapped data structures & serialization | +| [`@thi.ng/unionstruct`](./packages/unionstruct) | [![version](https://img.shields.io/npm/v/@thi.ng/unionstruct.svg)](https://www.npmjs.com/package/@thi.ng/unionstruct) | [changelog](./packages/unionstruct/CHANGELOG.md) | Wrapper for C-like structs / unions | +| [`@thi.ng/vector-pools`](./packages/vector-pools) | [![version](https://img.shields.io/npm/v/@thi.ng/vector-pools.svg)](https://www.npmjs.com/package/@thi.ng/vector-pools) | [changelog](./packages/vector-pools/CHANGELOG.md) | Data structures for memory mapped vectors | ### DSLs @@ -216,6 +242,7 @@ contribute, please first read [this document](./CONTRIBUTING.md). |-------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|-----------------------------------------------| | [`@thi.ng/pointfree`](./packages/pointfree) | [![version](https://img.shields.io/npm/v/@thi.ng/pointfree.svg)](https://www.npmjs.com/package/@thi.ng/pointfree) | [changelog](./packages/pointfree/CHANGELOG.md) | Stack-based DSL & functional composition | | [`@thi.ng/pointfree-lang`](./packages/pointfree-lang) | [![version](https://img.shields.io/npm/v/@thi.ng/pointfree-lang.svg)](https://www.npmjs.com/package/@thi.ng/pointfree-lang) | [changelog](./packages/pointfree-lang/CHANGELOG.md) | Forth-like syntax layer for @thi.ng/pointfree | +| [`@thi.ng/sexpr`](./packages/sexpr) | [![version](https://img.shields.io/npm/v/@thi.ng/sexpr.svg)](https://www.npmjs.com/package/@thi.ng/sexpr) | [changelog](./packages/sexpr/CHANGELOG.md) | S-Expression parser & runtime infrastructure | ## Building @@ -272,3 +299,33 @@ yarn doc ## License © 2018 Karsten Schmidt // Apache Software License 2.0 + +## Contributors ✨ + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + + + + + + + +
Karsten Schmidt
Karsten Schmidt

πŸ’» πŸ“– 🚧
Alberto
Alberto

πŸ’» πŸ’‘ πŸ›
Arthur Carabott
Arthur Carabott

πŸ’» πŸ€” πŸ’‘ πŸ“
AndrΓ© Wachter
AndrΓ© Wachter

πŸ’» πŸ€” πŸ›
Gavin Cannizzaro
Gavin Cannizzaro

πŸ’» πŸ› πŸ€”
Logan Powell
Logan Powell

πŸ“– πŸ› πŸ€”
Marcin Ignac
Marcin Ignac

πŸ›
arcticnoah
arcticnoah

πŸ’»
allforabit
allforabit

πŸ› πŸ’» πŸ€”
Yifan Wu
Yifan Wu

πŸ› πŸ“–
stwind
stwind

πŸ’» πŸ›
evilive
evilive

πŸ’»
Bnaya Peretz
Bnaya Peretz

πŸ’» πŸ› πŸ€”
+ + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/assets/dot/crypto-dflow.dot b/assets/diagrams/crypto-dflow.dot similarity index 100% rename from assets/dot/crypto-dflow.dot rename to assets/diagrams/crypto-dflow.dot diff --git a/assets/dot/dot-example.dot b/assets/diagrams/dot-example.dot similarity index 100% rename from assets/dot/dot-example.dot rename to assets/diagrams/dot-example.dot diff --git a/assets/dot/hdom-canvas-shapes.dot b/assets/diagrams/hdom-canvas-shapes.dot similarity index 100% rename from assets/dot/hdom-canvas-shapes.dot rename to assets/diagrams/hdom-canvas-shapes.dot diff --git a/assets/dot/hdom-v5.xml b/assets/diagrams/hdom-v5.xml similarity index 100% rename from assets/dot/hdom-v5.xml rename to assets/diagrams/hdom-v5.xml diff --git a/assets/dot/hdom.xml b/assets/diagrams/hdom.xml similarity index 100% rename from assets/dot/hdom.xml rename to assets/diagrams/hdom.xml diff --git a/assets/dot/iceps-dataflow.xml b/assets/diagrams/iceps-dataflow.xml similarity index 100% rename from assets/dot/iceps-dataflow.xml rename to assets/diagrams/iceps-dataflow.xml diff --git a/assets/dot/q1.dot b/assets/diagrams/q1.dot similarity index 100% rename from assets/dot/q1.dot rename to assets/diagrams/q1.dot diff --git a/assets/dot/q2.dot b/assets/diagrams/q2.dot similarity index 100% rename from assets/dot/q2.dot rename to assets/diagrams/q2.dot diff --git a/assets/dot/rle-layout.dot b/assets/diagrams/rle-layout.dot similarity index 100% rename from assets/dot/rle-layout.dot rename to assets/diagrams/rle-layout.dot diff --git a/assets/dot/rs-dflow.dot b/assets/diagrams/rs-dflow.dot similarity index 100% rename from assets/dot/rs-dflow.dot rename to assets/diagrams/rs-dflow.dot diff --git a/assets/dot/rs-dot-example.dot b/assets/diagrams/rs-dot-example.dot similarity index 100% rename from assets/dot/rs-dot-example.dot rename to assets/diagrams/rs-dot-example.dot diff --git a/assets/dot/rs-query1.dot b/assets/diagrams/rs-query1.dot similarity index 100% rename from assets/dot/rs-query1.dot rename to assets/diagrams/rs-query1.dot diff --git a/assets/dot/rstream-constructs.xml b/assets/diagrams/rstream-constructs.xml similarity index 100% rename from assets/dot/rstream-constructs.xml rename to assets/diagrams/rstream-constructs.xml diff --git a/assets/dot/transducers-hdom-dflow.dot b/assets/diagrams/transducers-hdom-dflow.dot similarity index 100% rename from assets/dot/transducers-hdom-dflow.dot rename to assets/diagrams/transducers-hdom-dflow.dot diff --git a/assets/diagrams/wolfram-dflow.dot b/assets/diagrams/wolfram-dflow.dot new file mode 100644 index 0000000000..a82eb21c41 --- /dev/null +++ b/assets/diagrams/wolfram-dflow.dot @@ -0,0 +1,54 @@ +digraph g { +rankdir=LR; +node[fontname=Inconsolata,fontsize=11,style=filled,fontcolor=white]; +edge[fontname=Inconsolata,fontsize=11]; +s0[label="rule\n(Stream)", color="blue"]; +s1[label="xform-2", color="black"]; +s2[label="in-rule", color="black"]; +s3[label="", color="gray"]; +s4[label="wolfram\n(StreamSync)", color="red"]; +s5[label="xform-7", color="black"]; +s6[label="in-sim", color="black"]; +s7[label="", color="gray"]; +s8[label="main\n(StreamSync)", color="red"]; +s9[label="hdom", color="black"]; +s10[label="xform-10", color="black"]; +s11[label="sidetoggle-11", color="black"]; +s12[label="obj export", color="black"]; +s13[label="in-id", color="black"]; +s14[label="", color="gray"]; +s15[label="kernel\n(Stream)", color="blue"]; +s16[label="xform-3", color="black"]; +s17[label="in-kernel", color="black"]; +s18[label="", color="gray"]; +s19[label="in-ksize", color="black"]; +s20[label="", color="gray"]; +s21[label="export trigger", color="blue"]; +s22[label="sub-12", color="black"]; +s23[label="", color="gray"]; +s23 -> s11[label="sidechain"]; +s8 -> s9[label="xform"]; +s7 -> s8[label="xform"]; +s6 -> s7; +s5 -> s6[label="xform"]; +s11 -> s12[label="xform"]; +s10 -> s11; +s4 -> s5[label="xform"]; +s4 -> s10[label="xform"]; +s3 -> s4[label="xform"]; +s2 -> s3; +s1 -> s2[label="xform"]; +s14 -> s8[label="xform"]; +s13 -> s14; +s0 -> s1[label="xform"]; +s0 -> s13[label="xform"]; +s18 -> s4[label="xform"]; +s17 -> s18; +s16 -> s17[label="xform"]; +s20 -> s8[label="xform"]; +s19 -> s20; +s15 -> s16[label="xform"]; +s15 -> s19[label="xform"]; +s22 -> s23; +s21 -> s22; +} \ No newline at end of file diff --git a/assets/dot/xml-converter.dot b/assets/diagrams/xml-converter.dot similarity index 100% rename from assets/dot/xml-converter.dot rename to assets/diagrams/xml-converter.dot diff --git a/assets/dot-example.png b/assets/dot/dot-example.png similarity index 100% rename from assets/dot-example.png rename to assets/dot/dot-example.png diff --git a/assets/screenshots/estuary.jpg b/assets/estuary/estuary.jpg similarity index 100% rename from assets/screenshots/estuary.jpg rename to assets/estuary/estuary.jpg diff --git a/assets/screenshots/canvas-dial.png b/assets/examples/canvas-dial.png similarity index 100% rename from assets/screenshots/canvas-dial.png rename to assets/examples/canvas-dial.png diff --git a/assets/examples/cellular-automata.png b/assets/examples/cellular-automata.png new file mode 100644 index 0000000000..3eea408f63 Binary files /dev/null and b/assets/examples/cellular-automata.png differ diff --git a/assets/screenshots/commit-table-ssr.png b/assets/examples/commit-table-ssr.png similarity index 100% rename from assets/screenshots/commit-table-ssr.png rename to assets/examples/commit-table-ssr.png diff --git a/assets/screenshots/crypto-chart.png b/assets/examples/crypto-chart.png similarity index 100% rename from assets/screenshots/crypto-chart.png rename to assets/examples/crypto-chart.png diff --git a/assets/crypto-dflow.png b/assets/examples/crypto-dflow.png similarity index 100% rename from assets/crypto-dflow.png rename to assets/examples/crypto-dflow.png diff --git a/assets/screenshots/gesture-analysis.png b/assets/examples/gesture-analysis.png similarity index 100% rename from assets/screenshots/gesture-analysis.png rename to assets/examples/gesture-analysis.png diff --git a/assets/screenshots/mandelbrot.jpg b/assets/examples/mandelbrot.jpg similarity index 100% rename from assets/screenshots/mandelbrot.jpg rename to assets/examples/mandelbrot.jpg diff --git a/assets/rs-dflow.png b/assets/examples/rs-dflow.png similarity index 100% rename from assets/rs-dflow.png rename to assets/examples/rs-dflow.png diff --git a/assets/rs-dot-example.svg b/assets/examples/rs-dot-example.svg similarity index 100% rename from assets/rs-dot-example.svg rename to assets/examples/rs-dot-example.svg diff --git a/assets/examples/rs-query1.png b/assets/examples/rs-query1.png new file mode 100644 index 0000000000..d9861ae32b Binary files /dev/null and b/assets/examples/rs-query1.png differ diff --git a/assets/rs-query1.svg b/assets/examples/rs-query1.svg similarity index 100% rename from assets/rs-query1.svg rename to assets/examples/rs-query1.svg diff --git a/assets/screenshots/rstream-grid.png b/assets/examples/rstream-grid.png similarity index 100% rename from assets/screenshots/rstream-grid.png rename to assets/examples/rstream-grid.png diff --git a/assets/examples/soa-ecs-100k.png b/assets/examples/soa-ecs-100k.png new file mode 100644 index 0000000000..9413c4c54d Binary files /dev/null and b/assets/examples/soa-ecs-100k.png differ diff --git a/assets/screenshots/svg-barchart.png b/assets/examples/svg-barchart.png similarity index 100% rename from assets/screenshots/svg-barchart.png rename to assets/examples/svg-barchart.png diff --git a/assets/screenshots/svg-waveform.png b/assets/examples/svg-waveform.png similarity index 100% rename from assets/screenshots/svg-waveform.png rename to assets/examples/svg-waveform.png diff --git a/assets/xml-converter.png b/assets/examples/xml-converter-dflow.png similarity index 100% rename from assets/xml-converter.png rename to assets/examples/xml-converter-dflow.png diff --git a/assets/screenshots/xml-converter.png b/assets/examples/xml-converter.png similarity index 100% rename from assets/screenshots/xml-converter.png rename to assets/examples/xml-converter.png diff --git a/assets/geom-isoline.png b/assets/geom/geom-isoline.png similarity index 100% rename from assets/geom-isoline.png rename to assets/geom/geom-isoline.png diff --git a/assets/geom/geom-isoline.svg b/assets/geom/geom-isoline.svg new file mode 100644 index 0000000000..072fcc566c --- /dev/null +++ b/assets/geom/geom-isoline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/geom-voronoi.jpg b/assets/geom/geom-voronoi.jpg similarity index 100% rename from assets/geom-voronoi.jpg rename to assets/geom/geom-voronoi.jpg diff --git a/assets/geom/tessel.png b/assets/geom/tessel.png new file mode 100644 index 0000000000..55c7dcd95c Binary files /dev/null and b/assets/geom/tessel.png differ diff --git a/assets/grid-iterators/diagonal2d-small.gif b/assets/grid-iterators/diagonal2d-small.gif new file mode 100644 index 0000000000..0e4cb761db Binary files /dev/null and b/assets/grid-iterators/diagonal2d-small.gif differ diff --git a/assets/grid-iterators/hilbert2d-small.gif b/assets/grid-iterators/hilbert2d-small.gif new file mode 100644 index 0000000000..5a39e6199e Binary files /dev/null and b/assets/grid-iterators/hilbert2d-small.gif differ diff --git a/assets/grid-iterators/interleavecolumns2d-small.gif b/assets/grid-iterators/interleavecolumns2d-small.gif new file mode 100644 index 0000000000..6f84e1b7f2 Binary files /dev/null and b/assets/grid-iterators/interleavecolumns2d-small.gif differ diff --git a/assets/grid-iterators/interleaverows2d-small.gif b/assets/grid-iterators/interleaverows2d-small.gif new file mode 100644 index 0000000000..c1ba6d8bea Binary files /dev/null and b/assets/grid-iterators/interleaverows2d-small.gif differ diff --git a/assets/grid-iterators/random2d-small.gif b/assets/grid-iterators/random2d-small.gif new file mode 100644 index 0000000000..d62c12819f Binary files /dev/null and b/assets/grid-iterators/random2d-small.gif differ diff --git a/assets/grid-iterators/spiral2d-small.gif b/assets/grid-iterators/spiral2d-small.gif new file mode 100644 index 0000000000..607ddd1ea8 Binary files /dev/null and b/assets/grid-iterators/spiral2d-small.gif differ diff --git a/assets/grid-iterators/zcurve2d-small.gif b/assets/grid-iterators/zcurve2d-small.gif new file mode 100644 index 0000000000..665b3ca44f Binary files /dev/null and b/assets/grid-iterators/zcurve2d-small.gif differ diff --git a/assets/grid-iterators/zigzagcolumns2d-small.gif b/assets/grid-iterators/zigzagcolumns2d-small.gif new file mode 100644 index 0000000000..8fa5ba3c3e Binary files /dev/null and b/assets/grid-iterators/zigzagcolumns2d-small.gif differ diff --git a/assets/grid-iterators/zigzagdiag2d-small.gif b/assets/grid-iterators/zigzagdiag2d-small.gif new file mode 100644 index 0000000000..20024bb053 Binary files /dev/null and b/assets/grid-iterators/zigzagdiag2d-small.gif differ diff --git a/assets/grid-iterators/zigzagrows2d-small.gif b/assets/grid-iterators/zigzagrows2d-small.gif new file mode 100644 index 0000000000..0b3a7a768e Binary files /dev/null and b/assets/grid-iterators/zigzagrows2d-small.gif differ diff --git a/assets/hdom-canvas/hdom-canvas-shapes-results.png b/assets/hdom-canvas/hdom-canvas-shapes-results.png new file mode 100644 index 0000000000..951545536c Binary files /dev/null and b/assets/hdom-canvas/hdom-canvas-shapes-results.png differ diff --git a/assets/hdom-canvas-shapes.png b/assets/hdom-canvas/hdom-canvas-shapes.png similarity index 100% rename from assets/hdom-canvas-shapes.png rename to assets/hdom-canvas/hdom-canvas-shapes.png diff --git a/assets/hdom-dataflow.png b/assets/hdom/hdom-dataflow.png similarity index 100% rename from assets/hdom-dataflow.png rename to assets/hdom/hdom-dataflow.png diff --git a/assets/screenshots/iges.png b/assets/iges/iges-houdini.png similarity index 100% rename from assets/screenshots/iges.png rename to assets/iges/iges-houdini.png diff --git a/assets/imgui/imgui-all.png b/assets/imgui/imgui-all.png new file mode 100644 index 0000000000..30bea6fc37 Binary files /dev/null and b/assets/imgui/imgui-all.png differ diff --git a/assets/imgui/imgui-demo.png b/assets/imgui/imgui-demo.png new file mode 100644 index 0000000000..66a002923a Binary files /dev/null and b/assets/imgui/imgui-demo.png differ diff --git a/assets/imgui/imgui-layout.drawio b/assets/imgui/imgui-layout.drawio new file mode 100644 index 0000000000..74788cc390 --- /dev/null +++ b/assets/imgui/imgui-layout.drawio @@ -0,0 +1 @@ +7VhNj5swFPw1SO0hK74Tjht2afcQaatIba9OcMCqwdQ4C+mvr41NCAHaVBvotkoOwR4/P5uZ4WGhWX5SfqAgi1ckhFgz9bDUrAfNNB1L5/8COEjAXtgSiCgKJWQ0wBr9gApU86I9CmHeCmSEYIayNrglaQq3rIUBSknRDtsR3F41AxHsAOstwF30CwpZLNGFozf4R4iiuF7Z0NVIAupgBeQxCElxAlmPmuVTQphsJaUPseCu5kXOCwZGjxujMGWXTPiMX7aB++l5/vX+m+tlIXvaoZnK8gLwXt0wBgeyZ3cpLNm792rn7FDTQck+DaHIqGvWsogRg+sMbMVowfXnWMwSzHsGb+5IygKQICy0f1queKpnDEt+WZGUiACEsU8woVVyK+A/3+e42hSkDJaDd2scOeTegySBjB54iJqwUKwf2t2i0dCulYpP9LMVBpRtomPihlneUOT+AdFmh2jNdDFfdZlnIG2R7H7fC09U9M12ir97TcjrgiSr+LEsm1/RJhGkSE4Twel5hJhV/VdSzXKplUiWEpoA3KzGW5G4nukv98hvWW5TxrzSFpOpblh/W3ZrUHahrihIkqVGh+5z0pGIFYTzl3c1kjlH0Mj3g+DRHUejnkfT8OwJNbJvGp1rZNZv6V+K5E4o0rxPJFnr0k2eqVJ3CfQaytUpA2yq2fp1yDbseYtss6dquT1Fy/DGInvx/5JtGs5vyXa8Kck2/pmjwWBNG+1sMGZN61O+t6aNp/zw6WBQ+YvFusI7DPHnj0q1NWfJ2fVtzXkYfpmN4AdKGGCIpLw78/SuQVxXHB+vVYfb/vCcHn/01GF3NH8Mn0xu/pjeHwvzrfnDufnjDfnDsL07ZzKL8G7z7agaO/kAZz3+BA== \ No newline at end of file diff --git a/assets/imgui/imgui-layout.png b/assets/imgui/imgui-layout.png new file mode 100644 index 0000000000..98bcc8b4c7 Binary files /dev/null and b/assets/imgui/imgui-layout.png differ diff --git a/assets/lsys-0.png b/assets/lsys/lsys-0.png similarity index 100% rename from assets/lsys-0.png rename to assets/lsys/lsys-0.png diff --git a/assets/lsys-1.png b/assets/lsys/lsys-1.png similarity index 100% rename from assets/lsys-1.png rename to assets/lsys/lsys-1.png diff --git a/assets/lsys-2.png b/assets/lsys/lsys-2.png similarity index 100% rename from assets/lsys-2.png rename to assets/lsys/lsys-2.png diff --git a/assets/lsys-3.png b/assets/lsys/lsys-3.png similarity index 100% rename from assets/lsys-3.png rename to assets/lsys/lsys-3.png diff --git a/assets/lsys-tree.png b/assets/lsys/lsys-tree.png similarity index 100% rename from assets/lsys-tree.png rename to assets/lsys/lsys-tree.png diff --git a/assets/malloc/compact-01.png b/assets/malloc/compact-01.png new file mode 100644 index 0000000000..67df49c8af Binary files /dev/null and b/assets/malloc/compact-01.png differ diff --git a/assets/malloc/compact-02.png b/assets/malloc/compact-02.png new file mode 100644 index 0000000000..dad61c01e0 Binary files /dev/null and b/assets/malloc/compact-02.png differ diff --git a/assets/malloc/compact-03.png b/assets/malloc/compact-03.png new file mode 100644 index 0000000000..078aae5cf8 Binary files /dev/null and b/assets/malloc/compact-03.png differ diff --git a/assets/malloc/malloc-layout.png b/assets/malloc/malloc-layout.png new file mode 100644 index 0000000000..90259f97a8 Binary files /dev/null and b/assets/malloc/malloc-layout.png differ diff --git a/assets/malloc/split-01.png b/assets/malloc/split-01.png new file mode 100644 index 0000000000..7039c867ea Binary files /dev/null and b/assets/malloc/split-01.png differ diff --git a/assets/malloc/split-02.png b/assets/malloc/split-02.png new file mode 100644 index 0000000000..d951de6de6 Binary files /dev/null and b/assets/malloc/split-02.png differ diff --git a/assets/pixel/pixel-basics.png b/assets/pixel/pixel-basics.png new file mode 100644 index 0000000000..02142f0835 Binary files /dev/null and b/assets/pixel/pixel-basics.png differ diff --git a/assets/screenshots/poisson.jpg b/assets/poisson/poisson.jpg similarity index 100% rename from assets/screenshots/poisson.jpg rename to assets/poisson/poisson.jpg diff --git a/assets/poisson/poisson.png b/assets/poisson/poisson.png new file mode 100644 index 0000000000..a576d311a5 Binary files /dev/null and b/assets/poisson/poisson.png differ diff --git a/assets/porter-duff-custom.png b/assets/porter-duff/porter-duff-custom.png similarity index 100% rename from assets/porter-duff-custom.png rename to assets/porter-duff/porter-duff-custom.png diff --git a/assets/porter-duff2.png b/assets/porter-duff/porter-duff2.png similarity index 100% rename from assets/porter-duff2.png rename to assets/porter-duff/porter-duff2.png diff --git a/assets/rle-layout.png b/assets/rle/rle-layout.png similarity index 100% rename from assets/rle-layout.png rename to assets/rle/rle-layout.png diff --git a/assets/rstream-merge.png b/assets/rstream/rstream-merge.png similarity index 100% rename from assets/rstream-merge.png rename to assets/rstream/rstream-merge.png diff --git a/assets/rstream-pubsub.png b/assets/rstream/rstream-pubsub.png similarity index 100% rename from assets/rstream-pubsub.png rename to assets/rstream/rstream-pubsub.png diff --git a/assets/rstream-sidechain-partition.png b/assets/rstream/rstream-sidechain-partition.png similarity index 100% rename from assets/rstream-sidechain-partition.png rename to assets/rstream/rstream-sidechain-partition.png diff --git a/assets/rstream-sidechain-toggle.png b/assets/rstream/rstream-sidechain-toggle.png similarity index 100% rename from assets/rstream-sidechain-toggle.png rename to assets/rstream/rstream-sidechain-toggle.png diff --git a/assets/rstream-sync.png b/assets/rstream/rstream-sync.png similarity index 100% rename from assets/rstream-sync.png rename to assets/rstream/rstream-sync.png diff --git a/assets/screenshots/pixel-basics.jpg b/assets/screenshots/pixel-basics.jpg deleted file mode 100644 index 31a74dd963..0000000000 Binary files a/assets/screenshots/pixel-basics.jpg and /dev/null differ diff --git a/assets/screenshots/shader-ast-01.jpg b/assets/shader-ast/shader-ast-01.jpg similarity index 100% rename from assets/screenshots/shader-ast-01.jpg rename to assets/shader-ast/shader-ast-01.jpg diff --git a/assets/screenshots/shader-ast-raymarch-compare.jpg b/assets/shader-ast/shader-ast-raymarch-compare.jpg similarity index 100% rename from assets/screenshots/shader-ast-raymarch-compare.jpg rename to assets/shader-ast/shader-ast-raymarch-compare.jpg diff --git a/assets/screenshots/shader-ast-raymarch-vex-sm.gif b/assets/shader-ast/shader-ast-raymarch-vex-sm.gif similarity index 100% rename from assets/screenshots/shader-ast-raymarch-vex-sm.gif rename to assets/shader-ast/shader-ast-raymarch-vex-sm.gif diff --git a/assets/transducers/hermite-tx.png b/assets/transducers/hermite-tx.png new file mode 100644 index 0000000000..70a7603e06 Binary files /dev/null and b/assets/transducers/hermite-tx.png differ diff --git a/examples/README.md b/examples/README.md index 197af60cf7..1797321eb6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -37,41 +37,45 @@ in touch via PR, issue tracker, email or twitter! | 26 | [hdom-vscroller](./hdom-vscroller) | virtual scroller component for large tables / lists | hdom | advanced | | 27 | [hmr-basics](./hmr-basics) | hdom & hot module replacement | hdom, memoize | basic | | 28 | [hydrate-basics](./hydrate-basics) | hiccup / hdom DOM hydration | hiccup, hdom | intermediate | -| 29 | [interceptor-basics](./interceptor-basics) | Event handling w/ interceptors and side effects | atom, hdom, interceptors | basic | -| 30 | [interceptor-basics2](./interceptor-basics2) | Event handling w/ interceptors and side effects | atom, hdom, interceptors | intermediate | -| 31 | [iso-plasma](./iso-plasma) | 2D contour line extraction & animation | geom, geom-isoline | intermediate | -| 32 | [json-components](./json-components) | JSON->component transformation, live editor | hdom, transducers | intermediate | -| 33 | [local-state](./local-state) | Local component state w/o HOF | hdom, paths | basic | -| 34 | [login-form](./login-form) | Basic SPA without router | atom, hdom | intermediate | -| 35 | [mandelbrot](./mandelbrot) | Worker-based mandelbrot fractal renderer | rstream, rstream-gestures, transducers-hdom | advanced | -| 36 | [markdown](./markdown) | Markdown parser & editor w/ live preview | fsm, rstream, transducers-hdom | advanced | -| 37 | [package-stats](./package-stats) | CLI util to visualize umbrella pkg stats | hiccup-svg, transducers | intermediate | -| 38 | [pixel-basics](./pixel-basics) | Pixel buffer manipulations | pixel | basic | -| 39 | [pointfree-svg](./pointfree-svg) | Generate SVG using pointfree DSL | hiccup, hiccup-svg, pointfree-lang | intermediate | -| 40 | [poly-spline](./poly-spline) | Polygon to cubic curve conversion & visualization | geom, hiccup-svg, hdom, rstream | intermediate | -| 41 | [porter-duff](./porter-duff) | Port-Duff image compositing / alpha blending | porter-duff, pixel | basic | -| 42 | [rotating-voronoi](./rotating-voronoi) | Animated Voronoi diagram, cubic splines & SVG download | geom, hdom, hdom-canvas, rstream, transducers | intermediate | -| 43 | [router-basics](./router-basics) | Complete mini SPA | atom, hdom, interceptors, router | advanced | -| 44 | [rstream-dataflow](./rstream-dataflow) | Dataflow graph | atom, hdom, rstream, rstream-gestures, rstream-graph, transducers | intermediate | -| 45 | [rstream-grid](./rstream-grid) | Dataflow graph SVG grid | atom, hdom, hiccup-svg, interceptors, rstream-graph, transducers | advanced | -| 46 | [rstream-hdom](./rstream-hdom) | rstream based UI updates & state handling | hdom, rstream, transducers | intermediate | -| 47 | [scenegraph](./scenegraph) | 2D scenegraph & shape picking | geom, hdom, hdom-canvas | intermediate | -| 48 | [scenegraph-image](./scenegraph-image) | 2D scenegraph & image map based geometry manipulation | geom, hdom, hdom-canvas, pixel | intermediate | -| 49 | [shader-ast-canvas2d](shader-ast-canvas2d) | 2D canvas shader emulation | shader-ast | basic | -| 50 | [shader-ast-noise](shader-ast-noise) | HOF shader function composition | shader-ast, webgl | basic | -| 51 | [shader-ast-raymarch](shader-ast-raymarch) | WebGL & Canvas2D raymarch shader | shader-ast, webgl | intermediate | -| 52 | [shader-ast-sdf2d](shader-ast-sdf2d) | WebGL & Canvas2D SDF | shader-ast, webgl | basic | -| 53 | [shader-ast-tunnel](shader-ast-tunnel) | WebGL & Canvas2D textured tunnel shader | shader-ast, webgl | basic | -| 54 | [svg-barchart](./svg-barchart) | hdom SVG barchart component | hdom, transducers | basic | -| 55 | [svg-particles](./svg-particles) | hdom SVG generation / animation | hdom, transducers | basic | -| 56 | [svg-waveform](./svg-waveform) | hdom SVG generation / undo history | atom, hdom, hiccup-svg, interceptors, iterators | intermediate | -| 57 | [talk-slides](./talk-slides) | Presentation slides from ClojureX 2018 | hdom, rstream, transducers-hdom | intermediate | -| 58 | [todo-list](./todo-list) | Canonical Todo list with undo/redo | atom, hdom, transducers | intermediate | -| 59 | [transducers-hdom](./transducers-hdom) | Transducer & rstream based hdom UI updates | hdom, rstream, transducers-hdom | basic | -| 60 | [triple-query](./triple-query) | Triple store query results & sortable table | atom, hdom, hdom-components, rstream-query, transducers | intermediate | -| 61 | [webgl-cubemap](./webgl-cubemap) | WebGL cubemap, async texture loading | hdom, webgl, shader-ast | intermediate | -| 62 | [webgl-gpgpu-basics](./webgl-gpgpu-basics) | Minimal GPGPU example | webgl, shader-ast | basic | -| 63 | [webgl-grid](./webgl-grid) | WebGL instancing | webgl, hdom | intermediate | -| 64 | [webgl-msdf](./webgl-msdf) | WebGL MSDF font rendering & particle system | webgl, webgl-msdf, shader-ast, hdom | intermediate | -| 65 | [webgl-ssao](./webgl-ssao) | WebGL screenspace ambient occlusion | webgl, shader-ast, rstream, hdom | advanced | -| 66 | [xml-converter](./xml-converter) | XML/HTML/SVG to hiccup conversion as you type | rstream, sax, transducers, transducers-hdom | advanced | +| 30 | [imgui](./imgui) | Canvas based immediate mode GUI | atom, hdom-canvas, imgui | advanced | +| 31 | [interceptor-basics](./interceptor-basics) | Event handling w/ interceptors and side effects | atom, hdom, interceptors | basic | +| 32 | [interceptor-basics2](./interceptor-basics2) | Event handling w/ interceptors and side effects | atom, hdom, interceptors | intermediate | +| 33 | [iso-plasma](./iso-plasma) | 2D contour line extraction & animation | geom, geom-isoline | intermediate | +| 34 | [json-components](./json-components) | JSON->component transformation, live editor | hdom, transducers | intermediate | +| 35 | [local-state](./local-state) | Local component state w/o HOF | hdom, paths | basic | +| 36 | [login-form](./login-form) | Basic SPA without router | atom, hdom | intermediate | +| 37 | [mandelbrot](./mandelbrot) | Worker-based mandelbrot fractal renderer | rstream, rstream-gestures, transducers-hdom | advanced | +| 38 | [markdown](./markdown) | Markdown parser & editor w/ live preview | fsm, rstream, transducers-hdom | advanced | +| 39 | [package-stats](./package-stats) | CLI util to visualize umbrella pkg stats | hiccup-svg, transducers | intermediate | +| 40 | [pixel-basics](./pixel-basics) | Pixel buffer manipulations | pixel | basic | +| 41 | [pointfree-svg](./pointfree-svg) | Generate SVG using pointfree DSL | hiccup, hiccup-svg, pointfree-lang | intermediate | +| 42 | [poly-spline](./poly-spline) | Polygon to cubic curve conversion & visualization | geom, hiccup-svg, hdom, rstream | intermediate | +| 43 | [porter-duff](./porter-duff) | Port-Duff image compositing / alpha blending | porter-duff, pixel | basic | +| 44 | [rotating-voronoi](./rotating-voronoi) | Animated Voronoi diagram, cubic splines & SVG download | geom, hdom, hdom-canvas, rstream, transducers | intermediate | +| 45 | [router-basics](./router-basics) | Complete mini SPA | atom, hdom, interceptors, router | advanced | +| 46 | [rstream-dataflow](./rstream-dataflow) | Dataflow graph | atom, hdom, rstream, rstream-gestures, rstream-graph, transducers | intermediate | +| 47 | [rstream-grid](./rstream-grid) | Dataflow graph SVG grid | atom, hdom, hiccup-svg, interceptors, rstream-graph, transducers | advanced | +| 48 | [rstream-hdom](./rstream-hdom) | rstream based UI updates & state handling | hdom, rstream, transducers | intermediate | +| 49 | [rstream-spreadsheet](./rstream-spreadsheet) | Spreadsheet w/ S-expr formula DSL | hdom, rstream-graph, sexpr, transducers | advanced | +| 50 | [scenegraph](./scenegraph) | 2D scenegraph & shape picking | geom, hdom, hdom-canvas | intermediate | +| 51 | [scenegraph-image](./scenegraph-image) | 2D scenegraph & image map based geometry manipulation | geom, hdom, hdom-canvas, pixel | intermediate | +| 52 | [shader-ast-canvas2d](shader-ast-canvas2d) | 2D canvas shader emulation | shader-ast | basic | +| 53 | [shader-ast-noise](shader-ast-noise) | HOF shader function composition | shader-ast, webgl | basic | +| 54 | [shader-ast-raymarch](shader-ast-raymarch) | WebGL & Canvas2D raymarch shader | shader-ast, webgl | intermediate | +| 55 | [shader-ast-sdf2d](shader-ast-sdf2d) | WebGL & Canvas2D SDF | shader-ast, webgl | basic | +| 56 | [shader-ast-tunnel](shader-ast-tunnel) | WebGL & Canvas2D textured tunnel shader | shader-ast, webgl | basic | +| 57 | [svg-barchart](./svg-barchart) | hdom SVG barchart component | hdom, transducers | basic | +| 58 | [svg-particles](./svg-particles) | hdom SVG generation / animation | hdom, transducers | basic | +| 59 | [svg-waveform](./svg-waveform) | hdom SVG generation / undo history | atom, hdom, hiccup-svg, interceptors, iterators | intermediate | +| 60 | [talk-slides](./talk-slides) | Presentation slides from ClojureX 2018 | hdom, rstream, transducers-hdom | intermediate | +| 61 | [todo-list](./todo-list) | Canonical Todo list with undo/redo | atom, hdom, transducers | intermediate | +| 62 | [transducers-hdom](./transducers-hdom) | Transducer & rstream based hdom UI updates | hdom, rstream, transducers-hdom | basic | +| 63 | [triple-query](./triple-query) | Triple store query results & sortable table | atom, hdom, hdom-components, rstream-query, transducers | intermediate | +| 64 | [webgl-cubemap](./webgl-cubemap) | WebGL cubemap, async texture loading | hdom, webgl, shader-ast | intermediate | +| 65 | [webgl-grid](./webgl-grid) | WebGL instancing | webgl, hdom | intermediate | +| 66 | [webgl-msdf](./webgl-msdf) | WebGL MSDF font rendering & particle system | webgl, webgl-msdf, shader-ast, hdom | intermediate | +| 67 | [webgl-multipass](./webgl-multipass) | Minimal multi-pass / GPGPU example | webgl, shader-ast, shader-ast-stdlib | intermediate | +| 68 | [webgl-shadertoy](./webgl-shadertoy) | Shadertoy-like example | webgl, shader-ast, shader-ast-stdlib | basic | +| 69 | [webgl-ssao](./webgl-ssao) | WebGL screenspace ambient occlusion | webgl, shader-ast, rstream, hdom | advanced | +| 70 | [wolfram](./wolfram) | 1D Wolfram automata | rstream, transducers, transducers-hdom | intermediate | +| 71 | [xml-converter](./xml-converter) | XML/HTML/SVG to hiccup conversion as you type | rstream, sax, transducers, transducers-hdom | advanced | diff --git a/examples/async-effect/package.json b/examples/async-effect/package.json index b20a7a9221..35ea028f69 100644 --- a/examples/async-effect/package.json +++ b/examples/async-effect/package.json @@ -11,9 +11,9 @@ "start": "yarn prep && parcel index.html -p 8080 --open -d out" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/bitmap-font/package.json b/examples/bitmap-font/package.json index 78729bf554..f97eef3307 100644 --- a/examples/bitmap-font/package.json +++ b/examples/bitmap-font/package.json @@ -10,10 +10,10 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "rimraf": "^2.6.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/canvas-dial/package.json b/examples/canvas-dial/package.json index da55003e1e..5f0715d7f7 100644 --- a/examples/canvas-dial/package.json +++ b/examples/canvas-dial/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/cellular-automata/package.json b/examples/cellular-automata/package.json index 85771d8c1a..626ae12513 100644 --- a/examples/cellular-automata/package.json +++ b/examples/cellular-automata/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest", diff --git a/examples/commit-table-ssr/package.json b/examples/commit-table-ssr/package.json index 40f075708d..f47acb7700 100644 --- a/examples/commit-table-ssr/package.json +++ b/examples/commit-table-ssr/package.json @@ -12,9 +12,9 @@ "start": "tsc && node build/server/index.js" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "express": "^4.16.3", diff --git a/examples/crypto-chart/README.md b/examples/crypto-chart/README.md index 5a4dc6df14..79615ce6aa 100644 --- a/examples/crypto-chart/README.md +++ b/examples/crypto-chart/README.md @@ -2,7 +2,7 @@ [Live demo](https://s3.amazonaws.com/demo.thi.ng/umbrella/crypto-chart/index.html) -![chart](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/crypto-chart.png) +![chart](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/crypto-chart.png) Price data provided by [cryptocompare.com](https://min-api.cryptocompare.com/). @@ -20,7 +20,7 @@ updates / diffs when there were any relevant upstream value changes. The diagram below shows a schematic of the dataflow graph used: -![dataflow](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/crypto-dflow.png) +![dataflow](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/crypto-dflow.png) ## Building diff --git a/examples/crypto-chart/package.json b/examples/crypto-chart/package.json index b20dc1c255..73f3ed9453 100644 --- a/examples/crypto-chart/package.json +++ b/examples/crypto-chart/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom-components": "latest", diff --git a/examples/dashboard/package.json b/examples/dashboard/package.json index 93747e4c32..37b4550b6b 100644 --- a/examples/dashboard/package.json +++ b/examples/dashboard/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest" diff --git a/examples/devcards/package.json b/examples/devcards/package.json index 0270f807ce..d451ce3eb7 100644 --- a/examples/devcards/package.json +++ b/examples/devcards/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/geom-convex-hull/package.json b/examples/geom-convex-hull/package.json index 618dd0c4a9..f151c9ea00 100644 --- a/examples/geom-convex-hull/package.json +++ b/examples/geom-convex-hull/package.json @@ -11,9 +11,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "terser": "^3.17.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/geom": "latest", diff --git a/examples/geom-convex-hull/src/webpack.d.ts b/examples/geom-convex-hull/src/webpack.d.ts new file mode 100644 index 0000000000..6e39ca7616 --- /dev/null +++ b/examples/geom-convex-hull/src/webpack.d.ts @@ -0,0 +1,3 @@ +declare module "*.jpg"; +declare module "*.png"; +declare module "*.svg"; diff --git a/examples/geom-knn/package.json b/examples/geom-knn/package.json index 784211b33b..a445b6f7e3 100644 --- a/examples/geom-knn/package.json +++ b/examples/geom-knn/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/bench": "latest", diff --git a/examples/geom-tessel/package.json b/examples/geom-tessel/package.json index 2c8e9f432b..9a34a6bb5f 100644 --- a/examples/geom-tessel/package.json +++ b/examples/geom-tessel/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/compose": "latest", diff --git a/examples/gesture-analysis/package.json b/examples/gesture-analysis/package.json index 4965165086..657d9daccb 100644 --- a/examples/gesture-analysis/package.json +++ b/examples/gesture-analysis/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/arrays": "latest", diff --git a/examples/webgl-gpgpu-basics/.gitignore b/examples/grid-iterators/.gitignore similarity index 100% rename from examples/webgl-gpgpu-basics/.gitignore rename to examples/grid-iterators/.gitignore diff --git a/examples/grid-iterators/README.md b/examples/grid-iterators/README.md new file mode 100644 index 0000000000..0d69adb255 --- /dev/null +++ b/examples/grid-iterators/README.md @@ -0,0 +1,13 @@ +# grid-iterators + +[Live demo](http://demo.thi.ng/umbrella/grid-iterators/) + +Please refer to the [example build instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) on the wiki. + +## Authors + +- Karsten Schmidt + +## License + +© 2019 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/webgl-gpgpu-basics/index.html b/examples/grid-iterators/index.html similarity index 66% rename from examples/webgl-gpgpu-basics/index.html rename to examples/grid-iterators/index.html index 92708882b3..f32d747f32 100644 --- a/examples/webgl-gpgpu-basics/index.html +++ b/examples/grid-iterators/index.html @@ -4,7 +4,7 @@ - webgl-gpgpu-basics + grid-iterators -

webgl-gpgpu-basics

-
Open console to view results...
+
+ diff --git a/examples/grid-iterators/package.json b/examples/grid-iterators/package.json new file mode 100644 index 0000000000..5b8086a27c --- /dev/null +++ b/examples/grid-iterators/package.json @@ -0,0 +1,29 @@ +{ + "name": "grid-iterators", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "clean": "rm -rf .cache build out", + "build": "yarn clean && parcel build index.html -d out --public-url ./ --no-source-maps --no-cache --detailed-report --experimental-scope-hoisting", + "build:webpack": "../../node_modules/.bin/webpack --mode production", + "start": "parcel index.html -p 8080 --open" + }, + "devDependencies": { + "parcel-bundler": "^1.12.4", + "terser": "^3.17.0", + "typescript": "^3.4.1" + }, + "dependencies": { + "@thi.ng/api": "latest", + "@thi.ng/rstream": "latest", + "@thi.ng/transducers-hdom": "latest" + }, + "browserslist": [ + "last 3 Chrome versions" + ], + "browser": { + "process": false + } +} \ No newline at end of file diff --git a/examples/grid-iterators/src/index.ts b/examples/grid-iterators/src/index.ts new file mode 100644 index 0000000000..af5f655ad7 --- /dev/null +++ b/examples/grid-iterators/src/index.ts @@ -0,0 +1,55 @@ +import { hueRgba, rgbaCss } from "@thi.ng/color"; +import { createElement } from "@thi.ng/hdom"; +import { concat, cycle } from "@thi.ng/transducers"; +import { + diagonal2d, + hilbert2d, + random2d, + spiral2d, + zcurve2d, + zigzagColumns2d, + // zigzagDiagonal2d, + zigzagRows2d +} from "@thi.ng/grid-iterators"; + +const W = 256; +const H = 256; +const canvas = ( + createElement(document.getElementById("app")!, "canvas", { + width: W, + height: H + }) +); +const ctx = canvas.getContext("2d")!; + +const NB: any = 16; +const BW = Math.ceil(W / NB); +const BH = Math.ceil(H / NB); +// create infinite sequence of all grid iterators +const buckets = cycle( + concat( + diagonal2d(NB, NB), + zigzagRows2d(NB), + hilbert2d(NB), + zigzagColumns2d(NB), + spiral2d(NB), + // zigzagDiagonal2d(NB), + zcurve2d(NB), + random2d(NB) + ) +); + +let frame = 0; +setInterval(() => { + const b = buckets.next(); + let [x, y] = b.value; + x *= BW; + y *= BH; + ctx.fillStyle = rgbaCss(hueRgba([], frame++ / (NB * NB))); + ctx.fillRect(x, y, BW, BH); +}, 16); + +// if (process.env.NODE_ENV !== "production") { +// const hot = (module).hot; +// hot && hot.dispose(() => {}); +// } diff --git a/examples/webgl-gpgpu-basics/tsconfig.json b/examples/grid-iterators/tsconfig.json similarity index 100% rename from examples/webgl-gpgpu-basics/tsconfig.json rename to examples/grid-iterators/tsconfig.json diff --git a/examples/webgl-gpgpu-basics/webpack.config.js b/examples/grid-iterators/webpack.config.js similarity index 100% rename from examples/webgl-gpgpu-basics/webpack.config.js rename to examples/grid-iterators/webpack.config.js diff --git a/examples/hdom-basics/package.json b/examples/hdom-basics/package.json index d583c34d2f..229f903c3a 100644 --- a/examples/hdom-basics/package.json +++ b/examples/hdom-basics/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest" diff --git a/examples/hdom-benchmark/package.json b/examples/hdom-benchmark/package.json index 463eeea674..4a97875734 100644 --- a/examples/hdom-benchmark/package.json +++ b/examples/hdom-benchmark/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest", diff --git a/examples/hdom-benchmark2/package.json b/examples/hdom-benchmark2/package.json index fafc57547d..f7e1682bbc 100644 --- a/examples/hdom-benchmark2/package.json +++ b/examples/hdom-benchmark2/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/binary": "latest", diff --git a/examples/hdom-benchmark2/src/index.ts b/examples/hdom-benchmark2/src/index.ts index 0310b29f03..7159cd9a16 100644 --- a/examples/hdom-benchmark2/src/index.ts +++ b/examples/hdom-benchmark2/src/index.ts @@ -72,21 +72,17 @@ const grid = { for (let i = 0; i < numChanges; i++) { const idx = (Math.random() * num) | 0; changed.add(idx); - changedRows.add(~~(idx / w)); + changedRows.add((idx / w) | 0); cells[idx] = (cells[idx] + 1) % 16; } const body = transduce( comp( mapIndexed((i, x) => [ "span", - isFirst || this.prevChanged.has(i) - ? { key: "c" + i, class: `cell cell-${x}` } - : changed.has(i) - ? { - key: "c" + i, - class: `cell xcell-${x}` - } - : { key: "c" + i, __skip: true } + { + key: "c" + i, + class: `cell ${changed.has(i) ? "xcell" : "cell"}-${x}` + } ]), partition(w), mapIndexed((i, row) => [ @@ -101,7 +97,7 @@ const grid = { ]) ), push(), - ["div"], + ["div", {}], cells ); let mergedCells = new Set(changed); diff --git a/examples/hdom-canvas-clock/package.json b/examples/hdom-canvas-clock/package.json index 9f77869ed5..5b5966d2df 100644 --- a/examples/hdom-canvas-clock/package.json +++ b/examples/hdom-canvas-clock/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest", diff --git a/examples/hdom-canvas-draw/package.json b/examples/hdom-canvas-draw/package.json index 33ee673417..3ea109ff9f 100644 --- a/examples/hdom-canvas-draw/package.json +++ b/examples/hdom-canvas-draw/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom-canvas": "latest", diff --git a/examples/hdom-canvas-shapes/README.md b/examples/hdom-canvas-shapes/README.md index 7b0b4bcf4a..9c2cde1cb4 100644 --- a/examples/hdom-canvas-shapes/README.md +++ b/examples/hdom-canvas-shapes/README.md @@ -1,5 +1,7 @@ # hdom-canvas-shapes +![screenshots](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/hdom-canvas/hdom-canvas-shapes-results.png) + [Live demo](http://demo.thi.ng/umbrella/hdom-canvas-shapes/) This example demonstrates different features of the upcoming @@ -30,7 +32,7 @@ Related examples: Dataflow diagram: -![dataflow](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/hdom-canvas-shapes.png) +![dataflow](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/hdom-canvas/hdom-canvas-shapes.png) ## Building diff --git a/examples/hdom-canvas-shapes/package.json b/examples/hdom-canvas-shapes/package.json index 559e46c1bc..53ba2c1802 100644 --- a/examples/hdom-canvas-shapes/package.json +++ b/examples/hdom-canvas-shapes/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open --no-cache" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom-canvas": "latest", diff --git a/examples/hdom-dropdown-fuzzy/package.json b/examples/hdom-dropdown-fuzzy/package.json index b1f696de41..bc1640dfef 100644 --- a/examples/hdom-dropdown-fuzzy/package.json +++ b/examples/hdom-dropdown-fuzzy/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/hdom-dropdown/package.json b/examples/hdom-dropdown/package.json index 7ae92df0d0..f940b84a59 100644 --- a/examples/hdom-dropdown/package.json +++ b/examples/hdom-dropdown/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/hdom-dyn-context/package.json b/examples/hdom-dyn-context/package.json index 4c2d5dce72..f2d7ab46f5 100644 --- a/examples/hdom-dyn-context/package.json +++ b/examples/hdom-dyn-context/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/atom": "latest", diff --git a/examples/hdom-inner-html/package.json b/examples/hdom-inner-html/package.json index 7be239679f..6b347a972a 100644 --- a/examples/hdom-inner-html/package.json +++ b/examples/hdom-inner-html/package.json @@ -10,10 +10,10 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "rimraf": "^2.6.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest" diff --git a/examples/hdom-localstate/package.json b/examples/hdom-localstate/package.json index c0a0cae783..86110668c6 100644 --- a/examples/hdom-localstate/package.json +++ b/examples/hdom-localstate/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "terser": "^3.17.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest", diff --git a/examples/hdom-skip-nested/.gitignore b/examples/hdom-skip-nested/.gitignore new file mode 100644 index 0000000000..0c5abcab62 --- /dev/null +++ b/examples/hdom-skip-nested/.gitignore @@ -0,0 +1,5 @@ +.cache +out +node_modules +yarn.lock +*.js diff --git a/examples/hdom-skip-nested/README.md b/examples/hdom-skip-nested/README.md new file mode 100644 index 0000000000..e8f5d28eb8 --- /dev/null +++ b/examples/hdom-skip-nested/README.md @@ -0,0 +1,13 @@ +# hdom-skip-nested + +[Live demo](http://demo.thi.ng/umbrella/hdom-skip-nested/) + +Please refer to the [example build instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) on the wiki. + +## Authors + +- Karsten Schmidt + +## License + +© 2019 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/hdom-skip-nested/index.html b/examples/hdom-skip-nested/index.html new file mode 100644 index 0000000000..a51ede8909 --- /dev/null +++ b/examples/hdom-skip-nested/index.html @@ -0,0 +1,36 @@ + + + + + + + hdom-skip-nested + + + + +
+

+ Click buttons to increase counters or click skip! to + toggle the __skip attrib for that component. +

+

+ If __skip is enabled, the counters remains + clickable, but no updates are shown until __skip is + disabled again. +

+
+
+ + + + diff --git a/examples/webgl-gpgpu-basics/package.json b/examples/hdom-skip-nested/package.json similarity index 77% rename from examples/webgl-gpgpu-basics/package.json rename to examples/hdom-skip-nested/package.json index 08bcab921e..ed82329ebf 100644 --- a/examples/webgl-gpgpu-basics/package.json +++ b/examples/hdom-skip-nested/package.json @@ -1,5 +1,5 @@ { - "name": "webgl-gpgpu-basics", + "name": "hdom-skip-nested", "version": "0.0.1", "repository": "https://github.com/thi-ng/umbrella", "author": "Karsten Schmidt ", @@ -12,13 +12,11 @@ }, "devDependencies": { "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "terser": "^3.17.0", + "typescript": "^3.4.1" }, "dependencies": { - "@thi.ng/shader-ast": "latest", - "@thi.ng/transducers": "latest", - "@thi.ng/webgl": "latest" + "@thi.ng/hdom": "latest" }, "browserslist": [ "last 3 Chrome versions" diff --git a/examples/hdom-skip-nested/src/index.ts b/examples/hdom-skip-nested/src/index.ts new file mode 100644 index 0000000000..bd42f5e27c --- /dev/null +++ b/examples/hdom-skip-nested/src/index.ts @@ -0,0 +1,92 @@ +import { ILifecycle, start } from "@thi.ng/hdom"; + +interface Counter extends ILifecycle { + id: number; + enabled: boolean; + previd: number; + prevenabled: boolean; +} +/** + * Button counter HOF component with __skip support + * Only renders if local state change requires it. + * If active & clicked, the button will be disabled for + * 500ms, and only then increments. + */ +const button = () => + { + init(_, __, id) { + this.enabled = true; + this.id = id; + }, + render(_, __) { + const body = [ + "button.dib.w3.pa2.bn", + { + __skip: + this.previd === this.id && + this.prevenabled === this.enabled, + disabled: this.enabled === false, + class: this.enabled + ? "bg-black white" + : "bg-moon-gray gray", + onclick: () => { + this.enabled = !this.enabled; + setTimeout(() => { + this.id++; + this.enabled = true; + }, 500); + } + }, + this.id + ]; + this.previd = this.id; + this.prevenabled = this.enabled; + return body; + } + }; + +/** + * Button wrapper HOF component (also with __skip support). + * If `__skip` is active for this component, any updates to the + * wrapped button component will not be shown until this + * wrapper's `__skip` attrib is disabled again (However, the + * counter still remains clickable). + */ +const wrapper = () => { + let skip = false; + let nextSkip = false; + const bt = button(); + return (_: any, id: number) => + skip + ? ["div", { __skip: true }] + : [ + `div.pv2.${nextSkip ? "bg-washed-red" : "bg-light-green"}`, + [ + "a.dib.w4.pa2.pointer", + { + onclick: () => { + nextSkip = !nextSkip; + requestAnimationFrame(() => (skip = nextSkip)); + } + }, + nextSkip ? "unskip!" : "skip!" + ], + [bt, id], + nextSkip + ? ["div.dib.mh3.f7.red", "(counter updates not shown)"] + : null + ]; +}; + +const app = () => { + const bt1 = wrapper(); + const bt2 = wrapper(); + return ["div.sans-serif", [bt1, 0], [bt2, 100]]; +}; + +const cancel = start(app()); + +if (process.env.NODE_ENV !== "production") { + const hot = (module).hot; + hot && hot.dispose(cancel); +} diff --git a/examples/hdom-skip-nested/tsconfig.json b/examples/hdom-skip-nested/tsconfig.json new file mode 100644 index 0000000000..bbf112cc18 --- /dev/null +++ b/examples/hdom-skip-nested/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "target": "es6", + "sourceMap": true + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/examples/hdom-skip/package.json b/examples/hdom-skip/package.json index 8ed6cb2485..4c0e7357b0 100644 --- a/examples/hdom-skip/package.json +++ b/examples/hdom-skip/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest" diff --git a/examples/hdom-theme-adr-0003/package.json b/examples/hdom-theme-adr-0003/package.json index e621264619..27a7e12dc1 100644 --- a/examples/hdom-theme-adr-0003/package.json +++ b/examples/hdom-theme-adr-0003/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/hdom-vscroller/package.json b/examples/hdom-vscroller/package.json index e84538643d..3c00a2192f 100644 --- a/examples/hdom-vscroller/package.json +++ b/examples/hdom-vscroller/package.json @@ -10,10 +10,10 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "rimraf": "^2.6.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/hmr-basics/package.json b/examples/hmr-basics/package.json index 9f8820339d..6d0181f2ee 100644 --- a/examples/hmr-basics/package.json +++ b/examples/hmr-basics/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/atom": "latest", diff --git a/examples/hydrate-basics/package.json b/examples/hydrate-basics/package.json index 7ebfe581e5..aa9e78ce0c 100644 --- a/examples/hydrate-basics/package.json +++ b/examples/hydrate-basics/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/atom": "latest", diff --git a/examples/imgui/.gitignore b/examples/imgui/.gitignore new file mode 100644 index 0000000000..0c5abcab62 --- /dev/null +++ b/examples/imgui/.gitignore @@ -0,0 +1,5 @@ +.cache +out +node_modules +yarn.lock +*.js diff --git a/examples/imgui/README.md b/examples/imgui/README.md new file mode 100644 index 0000000000..1fba44f555 --- /dev/null +++ b/examples/imgui/README.md @@ -0,0 +1,23 @@ +# imgui + +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/imgui/imgui-all.png) + +[Live demo](http://demo.thi.ng/umbrella/imgui/) + +WIP prototyping example for +[@thi.ng/imgui](https://github.com/thi-ng/umbrella/tree/master/packages/imgui) +and realisation via +[@thi.ng/hdom-canvas](https://github.com/thi-ng/umbrella/tree/master/packages/hdom-canvas). + +Please consult package readme for details. Also, please refer to the +[example build +instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) +on the wiki. + +## Authors + +- Karsten Schmidt + +## License + +© 2019 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/imgui/index.html b/examples/imgui/index.html new file mode 100644 index 0000000000..7087dbe5f0 --- /dev/null +++ b/examples/imgui/index.html @@ -0,0 +1,48 @@ + + + + + + + imgui + + + + + +
+
+
+
Ctrl/Cmd+Z ::
+
Undo
+
+
+
Shift+Ctrl/Cmd+Z ::
+
Redo
+
+
+
Ctrl ::
+
Show radial menu
+
+ Source code / Info +
+ + + diff --git a/examples/imgui/package.json b/examples/imgui/package.json new file mode 100644 index 0000000000..a4ff79e5b6 --- /dev/null +++ b/examples/imgui/package.json @@ -0,0 +1,29 @@ +{ + "name": "imgui", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "clean": "rm -rf .cache build out", + "build": "yarn clean && parcel build index.html -d out --public-url ./ --no-source-maps --no-cache --detailed-report --experimental-scope-hoisting", + "build:webpack": "../../node_modules/.bin/webpack --mode production", + "start": "parcel index.html -p 8080 --open" + }, + "devDependencies": { + "parcel-bundler": "^1.12.4", + "terser": "^3.17.0", + "typescript": "^3.4.1" + }, + "dependencies": { + "@thi.ng/api": "latest", + "@thi.ng/rstream": "latest", + "@thi.ng/transducers-hdom": "latest" + }, + "browserslist": [ + "last 3 Chrome versions" + ], + "browser": { + "process": false + } +} \ No newline at end of file diff --git a/examples/imgui/src/index.ts b/examples/imgui/src/index.ts new file mode 100644 index 0000000000..bf196acdf5 --- /dev/null +++ b/examples/imgui/src/index.ts @@ -0,0 +1,504 @@ +import { History, Atom } from "@thi.ng/atom"; +import { timedResult } from "@thi.ng/bench"; +import { line, pathFromSvg, normalizedPath } from "@thi.ng/geom"; +import { canvas } from "@thi.ng/hdom-canvas"; +import { DOWNLOAD, RESTART } from "@thi.ng/hiccup-carbon-icons"; +import { + buttonH, + buttonV, + DEFAULT_THEME, + dropdown, + GridLayout, + GUITheme, + iconButton, + IMGUI, + MouseButton, + NONE, + radialMenu, + radio, + ring, + sliderH, + sliderHGroup, + sliderVGroup, + textField, + textLabel, + textLabelRaw, + toggle, + xyPad, + Key, + gridLayout, + layoutBox, + dialGroup, + ringGroup +} from "@thi.ng/imgui"; +import { clamp, PI } from "@thi.ng/math"; +import { setInMany } from "@thi.ng/paths"; +import { sync, fromDOMEvent, sidechainPartition, fromRAF, merge, CloseMode, fromAtom } from "@thi.ng/rstream"; +import { gestureStream, GestureType } from "@thi.ng/rstream-gestures"; +import { float } from "@thi.ng/strings"; +import { step, map, comp, mapcat, iterator } from "@thi.ng/transducers"; +import { updateDOM } from "@thi.ng/transducers-hdom"; +import { sma } from "@thi.ng/transducers-stats"; +import { ZERO2, setC2, min2, Vec, vecOf, add2, hash } from "@thi.ng/vectors"; + +// define theme colors in RGBA format for future compatibility with +// WebGL backend +const THEMES: Partial[] = [ + DEFAULT_THEME, + { + globalBg: "#ccc", + focus: [1, 0.66, 0, 1], + cursor: [0, 0, 0, 1], + bg: [1, 1, 1, 0.66], + bgDisabled: [1, 1, 1, 0.33], + bgHover: [1, 1, 1, 0.9], + fg: [0.8, 0, 0.8, 1], + fgDisabled: [0.8, 0, 0.8, 0.5], + fgHover: [1, 0, 1, 1], + text: [0.3, 0.3, 0.3, 1], + textDisabled: [0.3, 0.3, 0.3, 0.5], + textHover: [0.2, 0.2, 0.4, 1], + bgTooltip: [1, 1, 0.8, 0.85], + textTooltip: [0, 0, 0, 1] + } +]; + +// float value formatters +const F1 = float(1); +const F2 = float(2); + +// UI constants +const FONT = "10px 'IBM Plex Mono'"; +const RADIO_LABELS = ["Yes", "No", "Maybe"]; +const RGB_LABELS = ["R", "G", "B"]; +const RGB_TOOLTIPS = ["Red", "Green", "Blue"]; +const RADIAL_LABELS = ["Buttons", "Slider", "Dials", "Dropdown", "Text"]; +const THEME_IDS = ["Default", "Raspberry"]; + +// helper function to normalize hiccup icon paths +// (transforms each path into one only consisting of cubic spline segments) +const mkIcon = (icon: any[]) => + [ + "g", { stroke: "none" }, + ...iterator( + comp(mapcat((p) => pathFromSvg(p[1].d)), map(normalizedPath)), + icon.slice(2) + ) + ]; + +// icon definitions (from @thi.ng/hiccup-carbon-icons) +const ICON1 = mkIcon(DOWNLOAD); +const ICON2 = mkIcon(RESTART); + +// main immutable app state wrapper (with time travel) +const DB = new History( + new Atom({ + uiVisible: true, + uiMode: 0, + theme: 0, + radius: 10, + gridW: 15, + rgb: [0.9, 0.45, 0.5], + pos: [400, 140], + txt: "Hello there! This is a test, do not panic!", + toggles: new Array(12).fill(false), + flags: [true, false], + radio: 0, + }), + // max. 500 undo steps + 500 +); + +// theme merging helper +const themeForID = (theme: number): Partial => + ({ ...THEMES[theme % THEMES.length], font: FONT, cursorBlink: 0 }); + +// state update handler for `rgb` value +// if Alt key is pressed when this handler executes, +// then all values will be set uniformly... +const setRGB = (gui: IMGUI, res: number[]) => + res !== undefined && + (gui.isAltDown() + ? DB.resetIn("rgb", vecOf(3, res[1])) + : DB.resetIn(["rgb", res[0]], res[1])); + +// main application +const app = () => { + let maxW = 240; + let size = [window.innerWidth, window.innerHeight]; + let radialPos = [0, 0]; + let radialActive = false; + + // GUI instance + const gui = new IMGUI({ theme: themeForID(DB.deref().theme) }); + + // GUI benchmark (moving average) transducer + const bench = step(sma(50)); + + // augment hdom-canvas component with init lifecycle method to + // attach event streams once canvas has been mounted + const _canvas = { + ...canvas, + init(canv: HTMLCanvasElement) { + // add event streams to main stream combinator + // in order to trigger GUI updates... + main.add( + // merge all event streams into a single input to `main` + // (we don't actually care about their actual values and merely + // use them as mechanism to trigger updates) + merge({ + src: [ + // mouse & touch events + gestureStream(canv, {}).subscribe({ + next(e) { + gui.setMouse( + [...e[1].pos], + e[0] === GestureType.START || e[0] === GestureType.DRAG + ? MouseButton.LEFT + : 0 + ) + } + }), + // keydown & undo/redo handler: + // Ctrl/Command + Z = undo + // Shift + Ctrl/Command + Z = redo + fromDOMEvent(window, "keydown").subscribe({ + next(e) { + if (e.key === Key.TAB) { + e.preventDefault(); + } + if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "z") { + e.shiftKey ? DB.redo() : DB.undo(); + } else { + gui.setKey(e); + } + } + }), + fromDOMEvent(window, "keyup").subscribe({ + next(e) { gui.setKey(e); } + }), + fromDOMEvent(window, "resize").subscribe({ + next() { + maxW = Math.min(maxW, window.innerWidth - 16); + setC2(size, window.innerWidth, window.innerHeight); + DB.swapIn("pos", (pos: Vec) => min2([], pos, size)); + } + }) + ] + }) + ); + } + }; + + // main GUI update function + const updateGUI = (draw: boolean) => { + // obtain atom value + const state = DB.deref(); + // setup initial layout (single column) + const grid = gridLayout(10, 10, maxW - 20, 1, 16, 4); + + gui.setTheme(themeForID(state.theme)); + + // start frame + gui.begin(draw); + + // disable all GUI components if radial menu is active + gui.beginDisabled(radialActive); + + // button components return true if clicked + if (buttonH(gui, grid, "show", state.uiVisible ? "Hide UI" : "Show UI")) { + DB.resetIn("uiVisible", !state.uiVisible); + } + if (state.uiVisible) { + let inner: GridLayout; + let inner2: GridLayout; + let res: any; + switch(state.uiMode) { + case 0: + // create empty row + grid.next(); + + // create 2-column layout in next row + inner = grid.nest(2); + // no actions for these buttons (demo only) + iconButton(gui, inner, "icon", ICON1, 14, 16, "Download", "Icon button"); + iconButton(gui, inner, "icon2", ICON2, 13, 16, "Restart", "Icon button"); + + grid.next(); + // text labels on their own never are non-interactive + textLabel(gui, grid, "Toggles:"); + + // create 8 column layout + inner = grid.nest(8); + // vertical button in 1st column and spanning 3 rows + if (buttonV(gui, inner, "toggleAll", 3, "INVERT")) { + DB.swapIn("toggles", (toggles: boolean[]) => toggles.map((x) => !x)); + } + + // create nested 4 column layout using remaining 7 columns of current layout + inner2 = inner.nest(4, [7, 1]); + // create toggle button for each array item + for(let i = 0; i < state.toggles.length; i++) { + if ((res = toggle(gui, inner2, `toggle${i}`, state.toggles[i], false, `${i}`)) !== undefined) { + DB.resetIn(["toggles", i], res); + } + } + + inner = grid.nest(2); + // temporarily use different theme by pushing on stack + gui.beginTheme(themeForID(state.theme + 1)); + if ((res = toggle(gui, inner, "opt1", state.flags[0], false, state.flags[0] ? "ON" : "OFF", "Unused")) !== undefined) { + DB.resetIn(["flags", 0], res); + } + if ((res = toggle(gui, inner, "opt2", state.flags[1], false, state.flags[1] ? "ON" : "OFF", "Unused")) !== undefined) { + DB.resetIn(["flags", 1], res); + } + // restore theme + gui.endTheme(); + + grid.next(); + // these next radio buttons are always disabled + gui.beginDisabled(); + textLabel(gui, grid, "Radio (horizontal):"); + radio(gui, grid, "radio1", true, state.radio, false, RADIO_LABELS); + gui.endDisabled(); + + grid.next(); + // alternative theme override for all components created by given function + if ((res = gui.withTheme(themeForID(state.theme + 1), () => radio(gui, grid, "radio2", true, state.radio, true, RADIO_LABELS))) !== undefined) { + DB.resetIn("radio", res); + } + + grid.next(); + textLabel(gui, grid, "Radio (vertical):"); + if ((res = radio(gui, grid, "radio3", false, state.radio, false, RADIO_LABELS)) !== undefined) { + DB.resetIn("radio", res); + } + grid.next(); + if ((res = radio(gui, grid, "radio4", false, state.radio, true, RADIO_LABELS)) !== undefined) { + DB.resetIn("radio", res); + } + break; + + case 1: + grid.next(); + textLabel(gui, grid, "Sliders:"); + + inner = grid.nest(2); + if ((res = sliderH(gui, inner, "grid", 1, 20, 1, state.gridW, "Grid", undefined, "Grid size")) !== undefined) { + DB.resetIn("gridW", res); + } + if ((res = sliderH(gui, inner, "rad", 2, 20, 1, state.radius, "Radius", undefined, "Dot radius")) !== undefined) { + DB.resetIn("radius", res); + } + + textLabel(gui, grid, "Slider groups:"); + textLabel(gui, grid, "(Alt + drag to adjust all):"); + + inner = grid.nest(4); + res = sliderVGroup(gui, inner, "col2", 0, 1, 0.05, state.rgb, 5, RGB_LABELS, F2, RGB_TOOLTIPS); + res = sliderVGroup(gui, inner, "col3", 0, 1, 0.05, state.rgb, 5, RGB_LABELS, F2, RGB_TOOLTIPS) || res; + res = sliderHGroup(gui, inner.nest(1, [2, 1]), "col", 0, 1, 0.05, false, state.rgb, RGB_LABELS, F2, RGB_TOOLTIPS) || res; + setRGB(gui, res); + + textLabel(gui, grid, "2D controller:"); + + inner = grid.nest(4); + res = xyPad(gui, inner, "xy1", ZERO2, size, 10, state.pos, 3, false, undefined, undefined, "Origin"); + res = xyPad(gui, inner, "xy2", ZERO2, size, 10, state.pos, 4, false, undefined, undefined, "Origin") || res; + res = xyPad(gui, inner, "xy3", ZERO2, size, 10, state.pos, -1, false, undefined, undefined, "Origin") || res; + res = xyPad(gui, inner, "xy4", ZERO2, size, 10, state.pos, -2, false, undefined, undefined, "Origin") || res; + res !== undefined && DB.resetIn("pos", res); + break; + + case 2: + grid.next(); + textLabel(gui, grid, "Dials:"); + + inner = grid.nest(6); + res = dialGroup(gui, inner, "dials1", 0, 1, 0.05, true, state.rgb, [], F1, RGB_TOOLTIPS); + res = dialGroup(gui, inner, "dials2", 0, 1, 0.05, true, state.rgb, [], F1, RGB_TOOLTIPS) || res; + setRGB(gui, res); + + inner = grid.nest(6); + if ((res = ring(gui, inner, "small", 0, 1, 0.05, state.rgb[0], PI, 0.5, "R", F2, "Red")) !== undefined) { + DB.resetIn(["rgb", 0], res); + } + if ((res = ring(gui, inner.nest(1, [2, 2]), "medium", 0, 1, 0.05, state.rgb[1], PI, 0.5, "G", F2, "Green")) !== undefined) { + DB.resetIn(["rgb", 1], res); + } + if ((res = ring(gui, inner.nest(1, [3, 3]), "large", 0, 1, 0.05, state.rgb[2], PI, 0.5, "B", F2, "Blue")) !== undefined) { + DB.resetIn(["rgb", 2], res); + } + + inner = grid.nest(3); + res = ringGroup(gui,inner,"rings1", 0, 1, 0.05, true, PI * 0.75, 0.5, state.rgb, RGB_LABELS, F2, RGB_TOOLTIPS); + res = ringGroup(gui,inner,"rings2", 0, 1, 0.05, true, PI * 0.5, 0.75, state.rgb, RGB_LABELS, F2, RGB_TOOLTIPS) || res; + res = ringGroup(gui,inner,"rings3", 0, 1, 0.05, true, PI * 0.25, 0.9, state.rgb, RGB_LABELS, F2, RGB_TOOLTIPS) || res; + setRGB(gui, res); + break; + + case 3: + grid.next(); + textLabel(gui, grid, "Select theme:"); + if ((res = dropdown(gui, grid, "theme", state.theme, THEME_IDS, "GUI theme")) !== undefined) { + DB.resetIn("theme", res); + } + const box = layoutBox(10, 150, 150, 120, 200, 24, 0); + if ((res = dropdown(gui, box, "theme2", state.theme, THEME_IDS, "GUI theme")) !== undefined) { + DB.resetIn("theme", res); + } + break; + + case 4: + grid.next(); + textLabel(gui, grid, "Editable textfield:"); + if ((res = textField(gui, grid, "txt", state.txt, undefined, "Type something...")) !== undefined) { + DB.resetIn("txt", res); + } + break; + + default: + } + } + // remove disabled flag from stack + gui.endDisabled(); + + // radial menu + if (gui.isControlDown()) { + if (!radialActive) { + radialPos = [...gui.mouse]; + } + // menu backdrop + gui.add( + gui.resource("radial", hash(radialPos) + 1, ()=> + ["g",{}, + ["radialGradient", + { id: "shadow", from: radialPos, to: radialPos, r1: 5, r2: 300}, + [[0, [1, 1, 1, 0.8]], [0.5, [1, 1, 1, 0.66]], [1, [1, 1, 1, 0]]] + ], + ["circle", { fill: "$shadow" }, radialPos, 300] + ] + ) + ); + let res: number | undefined; + if ((res = radialMenu(gui, "radial", radialPos[0], radialPos[1], 100, RADIAL_LABELS, [])) !== undefined) { + DB.swap((db) => setInMany(db, "uiMode", res, "uiVisible", true)); + } + gui.add( + textLabelRaw( + add2([], radialPos, [0, 120]), + { fill: "#000", align: "center" }, + "Use cursor keys to navigate" + ), + textLabelRaw( + add2([], radialPos, [0, 134]), + { fill: "#000", align: "center" }, + "Click or Enter to switch UI" + ) + ); + if (!radialActive) { + gui.focusID = gui.hotID; + } + radialActive = true; + } else { + radialActive = false; + } + // resize + const [w,h] = size; + if ( + gui.activeID === NONE && + gui.isMouseDown() && + Math.abs(gui.mouse[0] - maxW) < 80 + ) { + maxW = clamp(gui.mouse[0], 240, w - 16); + } + + const { key, hotID, activeID, focusID, lastID } = gui; + const statLayout = gridLayout(10, h - 10 - 3 * 14, w, 1, 14, 0); + textLabel(gui, statLayout, `Key: ${key}`); + textLabel(gui, statLayout, `Focus: ${focusID} / ${lastID}`); + textLabel(gui, statLayout, `IDs: ${hotID || "none"} / ${activeID || "none"}`); + + gui.end(); + }; + + // main component function + return () => { + const width = window.innerWidth; + const height = window.innerHeight; + + // this is only needed because we're NOT using a RAF update loop: + // call updateGUI twice to compensate for lack of regular 60fps update + // Note: Unless your GUI is super complex, this cost is pretty neglible + // and no actual drawing takes place here ... + + // the `timedResult` function measures execution time and returns tuple + // of [result, time]. We then pass the time taken to our SMA transducer + // to update and return a moving average. + const t = bench( + timedResult(() => { + updateGUI(false); + updateGUI(true); + } + )[1]); + // since the MA will only be available after the configured period, + // we will only display stats when they're ready... + t != null && gui.add(textLabelRaw([10, height - 10 - 4 * 14], "#ff0", `GUI time: ${F2(t)}ms`)); + // return hdom-canvas component with embedded GUI + return [ + _canvas, + { + width, + height, + style: { background: gui.theme.globalBg, cursor: gui.cursor }, + oncontextmenu: (e: Event) => e.preventDefault(), + ...gui.attribs + }, + // GUI resize border line + line([maxW, 0], [maxW, height], { stroke: "#000" }), + [ + "text", + { + transform: [0, -1, 1, 0, maxW + 12, height / 2], + fill: "#000", + font: FONT, + align: "center" + }, + [0, 0], + "DRAG TO RESIZE" + ], + // IMGUI implements IToHiccup interface so just supply as is + gui + ]; + }; +}; + +// main stream combinator +// the trigger() input is merely used to kick off the system +// once the 1st frame renders, the canvas component will create and attach +// event streams to this stream sync, which are then used to trigger future +// updates on demand... +const main = sync({ + src: { + state: fromAtom(DB) + }, + close: CloseMode.NEVER +}); + +// transform the stream: +main + // group potentially higher frequency event updates & sync with RAF + // to avoid extraneous real DOM/Canvas updates + .subscribe(sidechainPartition(fromRAF())) + // then apply main compoment function & apply hdom + .transform( + map(app()), + updateDOM() + ); + +// HMR handling / cleanup +if (process.env.NODE_ENV !== "production") { + const hot = (module).hot; + hot && hot.dispose(() => main.done()); +} diff --git a/examples/imgui/src/webpack.d.ts b/examples/imgui/src/webpack.d.ts new file mode 100644 index 0000000000..6e39ca7616 --- /dev/null +++ b/examples/imgui/src/webpack.d.ts @@ -0,0 +1,3 @@ +declare module "*.jpg"; +declare module "*.png"; +declare module "*.svg"; diff --git a/examples/imgui/tsconfig.json b/examples/imgui/tsconfig.json new file mode 100644 index 0000000000..bbf112cc18 --- /dev/null +++ b/examples/imgui/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "target": "es6", + "sourceMap": true + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/examples/imgui/webpack.config.js b/examples/imgui/webpack.config.js new file mode 100644 index 0000000000..08baf21e9f --- /dev/null +++ b/examples/imgui/webpack.config.js @@ -0,0 +1,21 @@ +module.exports = { + entry: "./src/index.ts", + output: { + filename: "bundle.[hash].js", + path: __dirname + "/out" + }, + resolve: { + extensions: [".ts", ".js"] + }, + module: { + rules: [ + { + test: /\.(png|jpg|gif)$/, + loader: "file-loader", + options: { name: "[path][hash].[ext]" } + }, + { test: /\.ts$/, use: "ts-loader" } + ] + }, + node: false +}; diff --git a/examples/interceptor-basics/package.json b/examples/interceptor-basics/package.json index 38afbd2ad2..8f3e76fc9f 100644 --- a/examples/interceptor-basics/package.json +++ b/examples/interceptor-basics/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/atom": "latest", diff --git a/examples/interceptor-basics2/package.json b/examples/interceptor-basics2/package.json index ddc230ca59..9e794fd779 100644 --- a/examples/interceptor-basics2/package.json +++ b/examples/interceptor-basics2/package.json @@ -10,14 +10,14 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", "@thi.ng/hdom": "latest", - "@thi.ng/intereptors": "latest", + "@thi.ng/interceptors": "latest", "@thi.ng/paths": "latest" }, "browserslist": [ diff --git a/examples/iso-plasma/package.json b/examples/iso-plasma/package.json index 0d4790f033..53e9ac4b1e 100644 --- a/examples/iso-plasma/package.json +++ b/examples/iso-plasma/package.json @@ -10,10 +10,10 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "rimraf": "^2.6.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/json-components/package.json b/examples/json-components/package.json index d79b31284e..e605368e50 100644 --- a/examples/json-components/package.json +++ b/examples/json-components/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest", diff --git a/examples/login-form/package.json b/examples/login-form/package.json index 80f379f8fd..158f7dec18 100644 --- a/examples/login-form/package.json +++ b/examples/login-form/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/atom": "latest", diff --git a/examples/mandelbrot/package.json b/examples/mandelbrot/package.json index 02fd2d18a6..005d047862 100644 --- a/examples/mandelbrot/package.json +++ b/examples/mandelbrot/package.json @@ -11,9 +11,9 @@ "start": "yarn build:worker && parcel index.html -d out -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/markdown/README.md b/examples/markdown/README.md index c49959c05f..785d4c6cd4 100644 --- a/examples/markdown/README.md +++ b/examples/markdown/README.md @@ -34,8 +34,10 @@ features and already sports: | Code block | GFM only (triple backtick prefix), w/ optional language hint | | Horiz. Rule | only dash supported (e.g. `---`), min 3 chars required | -Note: Currently, the last heading, paragraph, blockquote, list or table -requires an additional newline. +**Note: Because of MD's line break handling and the fact the parser only +consumes single characters from an iterable without knowledge of further +values, the last heading, paragraph, blockquote, list or table requires +an additional newline.** ### Limitations @@ -58,6 +60,8 @@ Some of these are considered, though currently not high priority... - **Functional:** parser entirely built using [transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) + (specifically those defined in + [@thi.ng/fsm](https://github.com/thi-ng/umbrella/tree/master/packages/fsm)) & function composition. Use the parser in a transducer pipeline to easily apply post-processing of the emitted results - **Declarative:** parsing rules defined declaratively with only minimal @@ -66,13 +70,11 @@ Some of these are considered, though currently not high priority... of hiccup-style tree nodes, ready to be used with [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/master/packages/hdom), [@thi.ng/hiccup](https://github.com/thi-ng/umbrella/tree/master/packages/hiccup) - or - [@thi.ng/hiccup-markdown](https://github.com/thi-ng/umbrella/tree/master/packages/hiccup-markdown) - (for back conversion to MD) + or the serializer of this package for back conversion to MD - **Customizable:** supports custom tag factory functions to override default behavior / representation of each parsed result element -- **Fast (enough):** parses this markdown file (5.8KB) in ~5ms on MBP2016 / Chrome 71 -- **Small:** minified + gzipped ~2.6KB +- **Fast (enough):** parses this markdown file (5.9KB) in ~5ms on MBP2016 / Chrome 71 +- **Small:** minified + gzipped ~2.5KB (parser sub-module incl. deps) See [example source code](https://github.com/thi-ng/umbrella/tree/master/examples/markdown/src/) @@ -161,4 +163,4 @@ on the wiki. ## License -© 2018 Karsten Schmidt // Apache Software License 2.0 +Β© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/markdown/README.txt b/examples/markdown/README.txt new file mode 100644 index 0000000000..785d4c6cd4 --- /dev/null +++ b/examples/markdown/README.txt @@ -0,0 +1,166 @@ +# Minimal Markdown parser + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + +## About + +This example is a test environment for the new & minimal +[Markdown](https://en.wikipedia.org/wiki/Markdown) parser & converter to +[hiccup](https://github.com/thi-ng/umbrella/tree/master/packages/hiccup) +format from the +[@thi.ng/hiccup-markdown](https://github.com/thi-ng/umbrella/tree/master/packages/hiccup-markdown) +package. + +The rest of this file is an excerpt of the relevant parts of that +package's `README.md`... + +### Features + +The parser itself is not aimed at supporting **all** of Markdown's +quirky syntax features, but will restrict itself to a sane subset of +features and already sports: + +| Feature | Comments | +|-------------|-----------------------------------------------------------------------------------------------------| +| Heading | ATX only (`#` line prefix), levels 1-6, then downgrade to paragraph | +| Paragraph | no support for `\` line breaks | +| Blockquote | Respects newlines | +| Format | **bold**, _emphasis_, `code`, ~~strikethrough~~ in paragraphs, headings, lists, blockquotes, tables | +| Link | no support for inline formats in label | +| Image | no image links | +| List | only unordered (`- ` line prefix), no nesting, supports line breaks | +| Table | no support for column alignment | +| Code block | GFM only (triple backtick prefix), w/ optional language hint | +| Horiz. Rule | only dash supported (e.g. `---`), min 3 chars required | + +**Note: Because of MD's line break handling and the fact the parser only +consumes single characters from an iterable without knowledge of further +values, the last heading, paragraph, blockquote, list or table requires +an additional newline.** + +### Limitations + +These MD features (and probably many more) are **not** supported: + +- inline HTML +- nested inline formats (e.g. **bold** inside _italic_) +- inline formats within link labels +- image links +- footnotes +- link references +- nested / ordered / numbered / todo lists + +Some of these are considered, though currently not high priority... + +> "Weeks of coding can **save hours** of planning." +> -- Anonymous + +### Other features + +- **Functional:** parser entirely built using + [transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) + (specifically those defined in + [@thi.ng/fsm](https://github.com/thi-ng/umbrella/tree/master/packages/fsm)) + & function composition. Use the parser in a transducer pipeline to + easily apply post-processing of the emitted results +- **Declarative:** parsing rules defined declaratively with only minimal + state/context handling needed +- **No regex:** consumes input character-wise and produces an iterator + of hiccup-style tree nodes, ready to be used with + [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/master/packages/hdom), + [@thi.ng/hiccup](https://github.com/thi-ng/umbrella/tree/master/packages/hiccup) + or the serializer of this package for back conversion to MD +- **Customizable:** supports custom tag factory functions to override + default behavior / representation of each parsed result element +- **Fast (enough):** parses this markdown file (5.9KB) in ~5ms on MBP2016 / Chrome 71 +- **Small:** minified + gzipped ~2.5KB (parser sub-module incl. deps) + +See [example source +code](https://github.com/thi-ng/umbrella/tree/master/examples/markdown/src/) +for reference... + +## Parsing & serializing to HTML + +```ts +import { iterator } from "@thi.ng/transducers"; +import { serialize } from "@thi.ng/hiccup"; + +import { parse } from "@thi.ng/hiccup-markdown"; + +const src = ` +# Hello world + +[This](http://example.com) is a _test_. + +`; + +// convert to hiccup tree +[...iterator(parse(), src)] +// [ [ 'h1', ' Hello world ' ], +// [ 'p', +// [ 'a', { href: 'http://example.com' }, 'This' ], +// ' is a ', +// [ 'em', 'test' ], +// '. ' ] ] + +// or serialize to HTML +serialize(iterator(parse(), src)); + +//

Hello world

+// This is a test.

+``` + +## Customizing tags + +The following interface defines factory functions for all supported +elements. User implementations / overrides can be given to the +`parseMD()` transducer to customize output. + +```ts +interface TagFactories { + blockquote(...children: any[]): any[]; + code(body: string): any[]; + codeblock(lang: string, body: string): any[]; + em(body: string): any[]; + heading(level, children: any[]): any[]; + hr(): any[]; + img(src: string, alt: string): any[]; + li(children: any[]): any[]; + link(href: string, body: string): any[]; + list(type: string, items: any[]): any[]; + paragraph(children: any[]): any[]; + strike(body: string): any[]; + strong(body: string): any[]; + table(rows: any[]): any[]; + td(i: number, children: any[]): any[]; + tr(i: number, cells: any[]): any[]; +} +``` + +Example with custom link elements: + +```ts +const tags = { + link: (href, body) => ["a.link.blue", { href }, body] +}; + +serialize(iterator(parse(tags), src)); + +//

Hello world

+//

This is a test.

+``` + +## Building locally + +Please refer to the [example build +instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) +on the wiki. + +## Authors + +- Karsten Schmidt + +## License + +Β© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/markdown/package.json b/examples/markdown/package.json index 05a8d3b8d7..d8771a116b 100644 --- a/examples/markdown/package.json +++ b/examples/markdown/package.json @@ -11,9 +11,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/markdown/src/index.ts b/examples/markdown/src/index.ts index 60b214b0a4..3c5084616c 100644 --- a/examples/markdown/src/index.ts +++ b/examples/markdown/src/index.ts @@ -63,7 +63,8 @@ const src = stream(); src.transform( map((src) => ({ src, - parsed: timedResult(() => [...iterator(parse(CUSTOM_TAGS), src)]) + // append exta newline to force last paragraph (see readme) + parsed: timedResult(() => [...iterator(parse(CUSTOM_TAGS), src + "\n")]) })), map(app(src)), updateDOM() diff --git a/examples/package-stats/package.json b/examples/package-stats/package.json index 31807dafa1..7e0841fe2a 100644 --- a/examples/package-stats/package.json +++ b/examples/package-stats/package.json @@ -9,9 +9,9 @@ "build": "yarn clean && ts-node src/index.ts" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3", + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4", "ts-node": "^8.2.0" }, "dependencies": { diff --git a/examples/pixel-basics/README.md b/examples/pixel-basics/README.md index d29365550a..62356bf182 100644 --- a/examples/pixel-basics/README.md +++ b/examples/pixel-basics/README.md @@ -2,7 +2,7 @@ [Live demo](http://demo.thi.ng/umbrella/pixel-basics/) -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/screenshots/pixel-basics.jpg) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/pixel/pixel-basics.png) Please refer to the [example build instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) on the wiki. diff --git a/examples/pixel-basics/package.json b/examples/pixel-basics/package.json index 4da98ef39d..ea248fe8f9 100644 --- a/examples/pixel-basics/package.json +++ b/examples/pixel-basics/package.json @@ -11,9 +11,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "terser": "^3.17.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/pixel": "latest", diff --git a/examples/pixel-basics/src/webpack.d.ts b/examples/pixel-basics/src/webpack.d.ts new file mode 100644 index 0000000000..891c762327 --- /dev/null +++ b/examples/pixel-basics/src/webpack.d.ts @@ -0,0 +1,2 @@ +declare module "*.jpg"; +declare module "*.png"; diff --git a/examples/pointfree-svg/package.json b/examples/pointfree-svg/package.json index 20e1008a86..f37e670aee 100644 --- a/examples/pointfree-svg/package.json +++ b/examples/pointfree-svg/package.json @@ -16,6 +16,6 @@ }, "devDependencies": { "ts-node": "^8.2.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" } } \ No newline at end of file diff --git a/examples/poly-spline/package.json b/examples/poly-spline/package.json index f0cf831434..0e87524b98 100644 --- a/examples/poly-spline/package.json +++ b/examples/poly-spline/package.json @@ -11,9 +11,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "terser": "^3.17.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/poly-spline/src/index.ts b/examples/poly-spline/src/index.ts index f1f88c686a..cdeef688eb 100644 --- a/examples/poly-spline/src/index.ts +++ b/examples/poly-spline/src/index.ts @@ -82,7 +82,7 @@ const app = ( _uniScale: Stream ) => ({ poly, mode, uniform, scale, uniScale }: any) => { // reconstruct poly as cubic curve segments - // reference: https://github.com/thi-ng/umbrella/tree/develop/packages/geom-splines#polygon-to-cubic-curve-conversion + // reference: https://github.com/thi-ng/umbrella/tree/master/packages/geom-splines#polygon-to-cubic-curve-conversion const cubics = asCubic(poly, { breakPoints: mode, scale: scale * (uniform ? uniScale : 1), diff --git a/examples/porter-duff/package.json b/examples/porter-duff/package.json index 0ebc5e5120..c3776fe1ad 100644 --- a/examples/porter-duff/package.json +++ b/examples/porter-duff/package.json @@ -11,9 +11,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "terser": "^3.17.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/pixel": "latest", diff --git a/examples/porter-duff/src/webpack.d.ts b/examples/porter-duff/src/webpack.d.ts new file mode 100644 index 0000000000..6e39ca7616 --- /dev/null +++ b/examples/porter-duff/src/webpack.d.ts @@ -0,0 +1,3 @@ +declare module "*.jpg"; +declare module "*.png"; +declare module "*.svg"; diff --git a/examples/rotating-voronoi/package.json b/examples/rotating-voronoi/package.json index 341fd01f5b..81128d6a3e 100644 --- a/examples/rotating-voronoi/package.json +++ b/examples/rotating-voronoi/package.json @@ -19,9 +19,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "terser": "^3.17.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/router-basics/package.json b/examples/router-basics/package.json index c369234113..2eba5e7d62 100644 --- a/examples/router-basics/package.json +++ b/examples/router-basics/package.json @@ -20,9 +20,9 @@ "@thi.ng/router": "latest" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "browserslist": [ "last 3 Chrome versions" diff --git a/examples/rstream-dataflow/README.md b/examples/rstream-dataflow/README.md index 4b28b9a965..60370e5078 100644 --- a/examples/rstream-dataflow/README.md +++ b/examples/rstream-dataflow/README.md @@ -8,7 +8,7 @@ on the wiki. ## About -![dataflow graph](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rs-dflow.png) +![dataflow graph](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/rs-dflow.png) This example combines the following packages to create & execute the above dataflow graph in a declarative manner. The diagram generation diff --git a/examples/rstream-dataflow/package.json b/examples/rstream-dataflow/package.json index 68a9e73381..d992ce96ab 100644 --- a/examples/rstream-dataflow/package.json +++ b/examples/rstream-dataflow/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/atom": "latest", diff --git a/examples/rstream-grid/package.json b/examples/rstream-grid/package.json index 77cd08f58b..d25f2e7197 100644 --- a/examples/rstream-grid/package.json +++ b/examples/rstream-grid/package.json @@ -25,9 +25,9 @@ }, "devDependencies": { "@types/node": "^9.6.2", - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "browserslist": [ "last 3 Chrome versions" diff --git a/examples/rstream-hdom/package.json b/examples/rstream-hdom/package.json index 7b2765eea6..8e7b2263ee 100644 --- a/examples/rstream-hdom/package.json +++ b/examples/rstream-hdom/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/arrays": "latest", diff --git a/examples/rstream-spreadsheet/.gitignore b/examples/rstream-spreadsheet/.gitignore new file mode 100644 index 0000000000..0c5abcab62 --- /dev/null +++ b/examples/rstream-spreadsheet/.gitignore @@ -0,0 +1,5 @@ +.cache +out +node_modules +yarn.lock +*.js diff --git a/examples/rstream-spreadsheet/README.md b/examples/rstream-spreadsheet/README.md new file mode 100644 index 0000000000..4be14cfda1 --- /dev/null +++ b/examples/rstream-spreadsheet/README.md @@ -0,0 +1,19 @@ +# rstream-spreadsheet + +[Live demo](http://demo.thi.ng/umbrella/rstream-spreadsheet/) + +Spreadsheet demo built w/ +[@thi.ng/rstream-graph](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-graph) +primitives and [S-expression based formula +DSL](https://github.com/thi-ng/umbrella/tree/master/packages/sexpr) to +dynamically define & edit the dataflow graph. + +Please refer to the [example build instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) on the wiki. + +## Authors + +- Karsten Schmidt + +## License + +© 2019 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/rstream-spreadsheet/index.html b/examples/rstream-spreadsheet/index.html new file mode 100644 index 0000000000..eb65c41002 --- /dev/null +++ b/examples/rstream-spreadsheet/index.html @@ -0,0 +1,61 @@ + + + + + + + rstream-spreadsheet + + + + +
+ +

S-expression formula syntax

+
    +
  • + a1 / + a1:c3 - cell ID / cell range + (case insensitive) +
  • +
  • + (* 10 (+ a1 a2:e2)) - 10 + times (cell A1 + entire row 2) +
  • +
  • + (min a1:c10) - min value of + columns A-C +
  • +
  • + (max a1:c10) - max value of + columns A-C +
  • +
  • + (avg a1:e5) - average value + of first 5 rows +
  • +
  • + (mag a1:e1) - vector + magnitude of row 1 +
  • +
  • + (abs a1) - absolute value of + cell A1 +
  • +
  • + (fit a1 a2 a3 a4 a5) - fit + cell value A1 from interval A2-A3 to A4-A5 +
  • +
+ + + diff --git a/examples/rstream-spreadsheet/package.json b/examples/rstream-spreadsheet/package.json new file mode 100644 index 0000000000..51f6cb1f5b --- /dev/null +++ b/examples/rstream-spreadsheet/package.json @@ -0,0 +1,29 @@ +{ + "name": "rstream-spreadsheet", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "clean": "rm -rf .cache build out", + "build": "yarn clean && parcel build index.html -d out --public-url ./ --no-source-maps --no-cache --detailed-report --experimental-scope-hoisting", + "build:webpack": "../../node_modules/.bin/webpack --mode production", + "start": "parcel index.html -p 8080 --open" + }, + "devDependencies": { + "parcel-bundler": "^1.12.4", + "terser": "^3.17.0", + "typescript": "^3.4.1" + }, + "dependencies": { + "@thi.ng/api": "latest", + "@thi.ng/rstream": "latest", + "@thi.ng/transducers-hdom": "latest" + }, + "browserslist": [ + "last 3 Chrome versions" + ], + "browser": { + "process": false + } +} \ No newline at end of file diff --git a/examples/rstream-spreadsheet/src/api.ts b/examples/rstream-spreadsheet/src/api.ts new file mode 100644 index 0000000000..17ee5c8f6c --- /dev/null +++ b/examples/rstream-spreadsheet/src/api.ts @@ -0,0 +1,24 @@ +import { ILifecycle } from "@thi.ng/hdom"; + +export const NUM_COLS = 5; +export const NUM_ROWS = 10; + +export const MAX_COL = "A".charCodeAt(0) + NUM_COLS - 1; + +export const CELL_STYLE = "div.dib.h2.pa2.ma0.br.bb.f7"; + +export const RE_CELL_ID = /^([A-Z])(\d+)$/i; +export const RE_CELL_RANGE = /^([A-Z])(\d+):([A-Z])(\d+)$/i; + +export interface Cell { + formula: string; + value: string | number; + error: string | null; + backup: string; + focus: boolean; +} + +export interface UICell extends ILifecycle { + element?: HTMLDivElement; + focus?: boolean; +} diff --git a/examples/rstream-spreadsheet/src/dsl.ts b/examples/rstream-spreadsheet/src/dsl.ts new file mode 100644 index 0000000000..d5470d6714 --- /dev/null +++ b/examples/rstream-spreadsheet/src/dsl.ts @@ -0,0 +1,258 @@ +import { Fn, IObjectOf } from "@thi.ng/api"; +import { defmulti } from "@thi.ng/defmulti"; +import { illegalArgs } from "@thi.ng/errors"; +import { fit } from "@thi.ng/math"; +import { memoize1 } from "@thi.ng/memoize"; +import { fromView } from "@thi.ng/rstream"; +import { + addNode, + node, + NodeInputSpec, + NodeSpec +} from "@thi.ng/rstream-graph"; +import { + ASTNode, + Implementations, + parse, + runtime, + Str, + Sym, + tokenize +} from "@thi.ng/sexpr"; +import { maybeParseFloat, Z2 } from "@thi.ng/strings"; +import { charRange } from "@thi.ng/strings"; +import { + add, + assocObj, + comp, + div, + filter, + map, + mapcat, + mapIndexed, + max, + mean, + min, + mul, + permutations, + range, + Reducer, + sub, + transduce +} from "@thi.ng/transducers"; +import { RE_CELL_ID, RE_CELL_RANGE } from "./api"; +import { DB, graph, removeCell } from "./state"; + +/** + * Runtime env, stores ID of result cell and tree/nesting depth of + * currently interpreted S-expression. + */ +interface Env { + id: string; + depth: number; +} + +// dynamic-dispatch function to delegate to actual DSL formula operators +const builtins = defmulti((x) => x.value); + +// init S-expression interpreter. this results in another +// dynamic-dispatch function which delegates based on AST node type +const rt = runtime, Env, any>({ + // As per Lisp convention, S-expressions are treated as function + // calls with 1st child item used as function name and rest as + // arguments. E.g. `(+ a1 b1 c1)` is a function call to the `+` + // function, with `a1`, `b1`, `c1` being arguments. + expr: (x, env) => + builtins(x.children[0], x.children, { + ...env, + depth: env.depth + 1 + }), + // other symbols are interpreted as cell IDs + sym: (x) => + RE_CELL_ID.test(x.value) + ? cellInput(x.value) + : illegalArgs("invalid cell ID"), + // strings & number used verbatim + str: (x) => ({ const: x.value }), + num: (x) => ({ const: x.value }) +}); + +/** + * S-expression evaluator. Parses and executes given s-expr and by doing + * so creates/updates the spreadsheet's dataflow graph. The `cellID` is + * used to store the result in the DB state atom. + * + * @param src + * @param cellID + */ +export const $eval = (src: string, cellID: string) => + rt(parse(tokenize(src)).children[0], { id: cellID, depth: 0 }); + +/** + * Takes a rstream-graph `NodeSpec` and array of AST nodes and an + * environment. For if the graph node corresponds to the top-level + * expression, the NodeSpec is configured to write the result of the + * computation back into the DB state atom. For inner/nested + * S-expressions we create an unique node ID to store the graph node in + * the graph. + * + * Any previously existing node for the resulting ID is first removed + * before the new one is created/added. + * + * @param spec + * @param vals + * @param env + */ +const defNode = (spec: NodeSpec, vals: ASTNode[], env: Env) => { + let id: string; + if (env.depth === 1) { + id = env.id; + spec.outs = { + "*": [id, "value"] + }; + } else { + id = JSON.stringify(vals); + if (graph[id]) { + return { stream: () => graph[id].node }; + } + } + removeCell(id); + const node = addNode(graph, DB, id, spec); + return { stream: () => node.node }; +}; + +/** + * Higher order function to define a DSL operator graph node with given + * transformation `fn`. Once added to the graph, that function is later + * called with a single arg, an object of numbered inputs like: `{ "00": + * 23, "01": 42 ... }` These inputs represent the resolved reactive + * argument values given in the original S-exression. For example, `(+ + * a1:c1 10)` will cause an object like: + * + * ``` + * { + * "00": curr value of a1, + * "01": curr value of b1, + * "02": curr value of c1, + * "03": 10, + * } + * ``` + * + * @param fn + */ +const defBuiltin = (fn: Fn, any>) => ( + _: ASTNode, + vals: ASTNode[], + env: Env +) => + defNode( + { + // wrapped transformation fn + fn: node(map(fn)), + // compile all s-expr arguments into a single object of input stream defs. + // - cell ranges yield multiple inputs + // - single cell IDs yield stream of cell's value + // - numeric args yield a single-item stream def of the given number + ins: transduce( + comp( + mapcat((i) => { + try { + return cellRangeInputs(i); + } catch (e) { + return [rt(i, env)]; + } + }), + mapIndexed((i, input) => [Z2(i), input]) + ), + assocObj(), + vals.slice(1) + ) + }, + vals, + env + ); + +/** + * Similar to `defBuiltin()`, but for reducer-based computations. Takes + * a no-arg function returning a reducer and an optional + * pre-transformer. The resulting transformation function filters out + * all empty cells. + * + * @param rfn + * @param xf + */ +const defReducer = ( + rfn: () => Reducer, + xf: Fn = (x) => x +) => + defBuiltin((ports: IObjectOf) => { + const keys = Object.keys(ports).sort(); + return transduce( + comp(map((k) => ports[k]), filter((x) => x != null), map(xf)), + rfn(), + xf(ports[keys.shift()!]), + keys + ); + }); + +/** + * Returns a rstream-graph NodeInputSpec linked to the cell value given + * by `id`, stored in the central DB state atom. The stream is + * configured to attempt string-to-number conversion of its values. + */ +const cellInput = memoize1( + (id: string): NodeInputSpec => ({ + stream: () => + fromView(DB, [id.toUpperCase(), "value"], (x) => + maybeParseFloat(x, null) + ) + }) +); + +/** + * Returns iterator of NodeInputSpecs for given cell range string. + * + * @param x + */ +const cellRangeInputs = (x: ASTNode) => { + const [acol, arow, bcol, brow] = parseCellIDRange(x); + return map<[string, number], NodeInputSpec>( + ([c, r]) => cellInput(`${c}${r}`), + permutations( + charRange(acol.toUpperCase(), bcol.toUpperCase()), + range(parseInt(arow), parseInt(brow) + 1) + ) + ); +}; + +/** + * Parses cell range string, e.g. `a5:c10` => ["a",5,"c",10] + * + * @param x + */ +const parseCellIDRange = (x: ASTNode) => { + const match = RE_CELL_RANGE.exec((x).value); + if (!match) illegalArgs("invalid cell range"); + return match!.slice(1, 5); +}; + +/** + * Register built-ins. + */ +builtins.addAll({ + "+": defReducer(add), + "*": defReducer(mul), + "-": defReducer(sub), + "/": defReducer(() => div(1)), + min: defReducer(min), + max: defReducer(max), + avg: defReducer(() => mean()), + mag: defReducer( + () => [() => 0, (acc) => Math.sqrt(acc), (acc, x) => acc + x], + (x) => x * x + ), + abs: defBuiltin(({ "00": x }) => Math.abs(x)), + fit: defBuiltin(({ "00": x, "01": a, "02": b, "03": c, "04": d }) => + fit(x, a, b, c, d) + ) +}); diff --git a/examples/rstream-spreadsheet/src/index.ts b/examples/rstream-spreadsheet/src/index.ts new file mode 100644 index 0000000000..0345d785ca --- /dev/null +++ b/examples/rstream-spreadsheet/src/index.ts @@ -0,0 +1,158 @@ +import { isNumber } from "@thi.ng/checks"; +import { fromAtom } from "@thi.ng/rstream"; +import { charRange } from "@thi.ng/strings"; +import { + comp, + map, + mapIndexed, + partition, + permutations, + push, + range, + transduce +} from "@thi.ng/transducers"; +import { updateDOM } from "@thi.ng/transducers-hdom"; +import { + CELL_STYLE, + MAX_COL, + NUM_COLS, + NUM_ROWS, + UICell +} from "./api"; +import { + blurCell, + cancelCell, + DB, + focusCell, + graph, + updateCell +} from "./state"; + +const formatCell = (x: string | number) => (isNumber(x) ? x.toFixed(2) : x); + +/** + * Choose background color based on cell state. + * + * @param cell + */ +const cellBackground = (cell: any) => + cell.focus + ? "bg-yellow" + : cell.formula + ? cell.error + ? "bg-red white" + : "bg-light-green" + : ""; + +/** + * thi.ng/hdom cell component with lifecycle methods. (The current + * markup used (editable div's) is far from perfect...) + * + * @param cellid tuple + */ +const cell = ([row, col]: [number, string]) => + { + init(el: HTMLDivElement) { + this.element = el; + this.focus = false; + }, + render(_: any, cells: any) { + const id = `${col}${row}`; + const cell = cells[id]; + return [ + `${CELL_STYLE}.w4.overflow-y-hidden.overflow-x-scroll`, + { + class: cellBackground(cell), + contenteditable: true, + title: cell.formula, + onfocus: () => { + this.focus = true; + focusCell(id); + }, + onblur: () => { + if (this.focus) { + updateCell(id, this.element!.textContent!.trim()); + this.focus = false; + } + blurCell(id); + }, + onkeydown: (e: KeyboardEvent) => { + switch (e.key) { + case "Enter": + case "Tab": + updateCell( + id, + this.element!.textContent!.trim() + ); + this.element!.blur(); + break; + case "Escape": + this.focus = false; + cancelCell(id); + this.element!.blur(); + } + } + }, + String( + cell.focus && cell.formula + ? cell.formula + : cell.error || formatCell(cell.value) + ) + ]; + } + }; + +/** + * Main UI component HOF. Attached to to `main` rstream (defined below) + * and called with the DB state atom's current state when it changes. + * The function returns a thi.ng/hdom component tree representing the + * entire spreadsheet. + */ +const app = () => { + const CELLS: UICell[][] = transduce( + comp(map(cell), partition(NUM_COLS)), + push(), + permutations(range(1, NUM_ROWS + 1), charRange("A", MAX_COL)) + ); + return (state: any) => [ + "div", + {}, + [`${CELL_STYLE}.w2.b.bg-moon-gray`, "\u00a0"], + map( + (col) => [`${CELL_STYLE}.w4.b.bg-moon-gray`, {}, col], + charRange("A", MAX_COL) + ), + mapIndexed( + (i, rowid) => [ + "div", + {}, + [ + `${CELL_STYLE}.w2.b.bg-moon-gray.overflow-y-hidden.overflow-x-scroll`, + {}, + rowid + ], + ...CELLS[i].map((cell) => [cell, state]) + ], + range(1, NUM_ROWS + 1) + ) + ]; +}; + +// setLogger(new ConsoleLogger("rstream")); + +// main state value subscription +const main = fromAtom(DB); + +// transform state value into UI components and then apply to DOM. +// due to the use of `contenteditable` div's for spreadsheet cells, we +// need to disable the use of automatic span wrappers for text content +// in hdom +main.transform(map(app()), updateDOM({ span: false })); + +(window)["DB"] = DB; +(window)["graph"] = graph; + +if (process.env.NODE_ENV !== "production") { + const hot = (module).hot; + hot && hot.dispose(() => main.done()); +} diff --git a/examples/rstream-spreadsheet/src/state.ts b/examples/rstream-spreadsheet/src/state.ts new file mode 100644 index 0000000000..a5cd373cd9 --- /dev/null +++ b/examples/rstream-spreadsheet/src/state.ts @@ -0,0 +1,94 @@ +import { IObjectOf } from "@thi.ng/api"; +import { Atom } from "@thi.ng/atom"; +import { setIn, setInMany } from "@thi.ng/paths"; +import { Node, removeNode } from "@thi.ng/rstream-graph"; +import { charRange } from "@thi.ng/strings"; +import { + assocObj, + map, + permutations, + range, + transduce +} from "@thi.ng/transducers"; +import { Cell, MAX_COL, NUM_ROWS } from "./api"; +import { $eval } from "./dsl"; + +/** + * Initializes state atom with default cell values. Later on, the + * dataflow graph operators defined by various formula expressions will + * then attach subscriptions manipulate individual cell values. + */ +export const DB = new Atom>( + transduce( + map(([col, row]) => [ + `${col}${row}`, + { formula: "", value: "", backup: "", focus: false, error: "" } + ]), + assocObj(), + permutations(charRange("A", MAX_COL), range(1, NUM_ROWS + 1)) + ) +); + +/** + * Container object for storing dataflow graph. + */ +export const graph: IObjectOf = {}; + +export const removeCell = (id: string) => removeNode(graph, id); + +/** + * Enables focus flag for given cell + * + * @param id + */ +export const focusCell = (id: string) => { + DB.swapIn(id, (cell: Cell) => + setInMany(cell, "focus", true, "backup", cell.formula) + ); +}; + +/** + * Disables focus flag for given cell + * + * @param id + */ +export const blurCell = (id: string) => { + DB.swapIn(id, (cell: Cell) => setIn(cell, "focus", false)); +}; + +/** + * Restores cell value to `backup` string and clears focus. + * + * @param id + */ +export const cancelCell = (id: string) => { + DB.swapIn(id, (cell: Cell) => + setInMany(cell, "focus", false, "formula", cell.backup) + ); +}; + +/** + * Cell on-change handler. Checks if `val` is potentially an + * S-expression and if so attempts to parse and execute it. Sets cell + * error if failed. If not an s-expr, updates cell value and clears + * error. + * + * @param id + * @param val + */ +export const updateCell = (id: string, val: string) => { + if (val.startsWith("(")) { + DB.resetIn([id, "formula"], val); + try { + $eval(val, id); + DB.resetIn([id, "error"], null); + } catch (e) { + DB.resetIn([id, "error"], e.message); + } + } else { + removeCell(id); + DB.swapIn(id, (cell) => + setInMany(cell, "value", val, "formula", "", "error", null) + ); + } +}; diff --git a/examples/rstream-spreadsheet/tsconfig.json b/examples/rstream-spreadsheet/tsconfig.json new file mode 100644 index 0000000000..bbf112cc18 --- /dev/null +++ b/examples/rstream-spreadsheet/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "target": "es6", + "sourceMap": true + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/examples/rstream-spreadsheet/webpack.config.js b/examples/rstream-spreadsheet/webpack.config.js new file mode 100644 index 0000000000..bf16021356 --- /dev/null +++ b/examples/rstream-spreadsheet/webpack.config.js @@ -0,0 +1,23 @@ +module.exports = { + entry: "./src/index.ts", + output: { + filename: "bundle.[hash].js", + path: __dirname + "/out" + }, + resolve: { + extensions: [".ts", ".js"] + }, + module: { + rules: [ + { + test: /\.(png|jpg|gif)$/, + loader: "file-loader", + options: { name: "[path][hash].[ext]" } + }, + { test: /\.ts$/, use: "ts-loader" } + ] + }, + node: { + process: false + } +}; diff --git a/examples/scenegraph-image/package.json b/examples/scenegraph-image/package.json index a82d4bd763..25f72fc783 100644 --- a/examples/scenegraph-image/package.json +++ b/examples/scenegraph-image/package.json @@ -11,9 +11,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "terser": "^3.17.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/scenegraph-image/src/webpack.d.ts b/examples/scenegraph-image/src/webpack.d.ts new file mode 100644 index 0000000000..65e20c0a38 --- /dev/null +++ b/examples/scenegraph-image/src/webpack.d.ts @@ -0,0 +1,2 @@ +declare module "*.png"; +declare module "*.jpg"; diff --git a/examples/scenegraph/package.json b/examples/scenegraph/package.json index 912a63d46d..3da274806b 100644 --- a/examples/scenegraph/package.json +++ b/examples/scenegraph/package.json @@ -11,9 +11,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "terser": "^3.17.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/shader-ast-canvas2d/package.json b/examples/shader-ast-canvas2d/package.json index 3c4bc5afac..902ff28856 100644 --- a/examples/shader-ast-canvas2d/package.json +++ b/examples/shader-ast-canvas2d/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/shader-ast": "latest", diff --git a/examples/shader-ast-canvas2d/src/index.ts b/examples/shader-ast-canvas2d/src/index.ts index 3ecb1933bf..815317eebd 100644 --- a/examples/shader-ast-canvas2d/src/index.ts +++ b/examples/shader-ast-canvas2d/src/index.ts @@ -17,7 +17,7 @@ import { vec4 } from "@thi.ng/shader-ast"; import { targetGLSL } from "@thi.ng/shader-ast-glsl"; -import { initRuntime, targetJS } from "@thi.ng/shader-ast-js"; +import { canvasRenderer, targetJS } from "@thi.ng/shader-ast-js"; import { fit0111, fit1101 } from "@thi.ng/shader-ast-stdlib"; const js = targetJS(); @@ -80,10 +80,10 @@ canvas.width = W; canvas.height = H; document.body.appendChild(canvas); -const rt = initRuntime({ canvas }); +const rt = canvasRenderer(canvas); const t0 = Date.now(); setInterval(() => { const time = (Date.now() - t0) * 0.001; - rt.update((frag) => fn(frag, size, time)); + rt((frag) => fn(frag, size, time)); }, 16); diff --git a/examples/shader-ast-noise/index.html b/examples/shader-ast-noise/index.html index 7a1fc0bfb0..c95735abab 100644 --- a/examples/shader-ast-noise/index.html +++ b/examples/shader-ast-noise/index.html @@ -27,7 +27,7 @@

Source code

diff --git a/examples/shader-ast-noise/package.json b/examples/shader-ast-noise/package.json index ba424c8207..ae2649de0a 100644 --- a/examples/shader-ast-noise/package.json +++ b/examples/shader-ast-noise/package.json @@ -11,9 +11,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "terser": "^3.17.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/shader-ast": "latest", diff --git a/examples/shader-ast-noise/src/index.ts b/examples/shader-ast-noise/src/index.ts index 0e93ea7370..aa83c7d7a1 100644 --- a/examples/shader-ast-noise/src/index.ts +++ b/examples/shader-ast-noise/src/index.ts @@ -15,7 +15,7 @@ import { vec4 } from "@thi.ng/shader-ast"; import { GLSLVersion, targetGLSL } from "@thi.ng/shader-ast-glsl"; -import { initRuntime, targetJS } from "@thi.ng/shader-ast-js"; +import { canvasRenderer, targetJS } from "@thi.ng/shader-ast-js"; import { additive, aspectCorrectedUV, @@ -82,12 +82,12 @@ if (JS_MODE) { // JS Canvas 2D shader emulation from here... // const fn = JS.compile(shaderProgram).mainImage; - const rt = initRuntime({ canvas }); + const rt = canvasRenderer(canvas); let time = 0; setInterval(() => { time += 0.01; - rt.update((frag) => fn(frag, size, time)); + rt((frag) => fn(frag, size, time)); }, 16); } else { // diff --git a/examples/shader-ast-raymarch/index.html b/examples/shader-ast-raymarch/index.html index 4df683ba16..c030e2f6b8 100644 --- a/examples/shader-ast-raymarch/index.html +++ b/examples/shader-ast-raymarch/index.html @@ -27,7 +27,7 @@

Source code

diff --git a/examples/shader-ast-raymarch/package.json b/examples/shader-ast-raymarch/package.json index 6d5f446ebf..b8340a0fb2 100644 --- a/examples/shader-ast-raymarch/package.json +++ b/examples/shader-ast-raymarch/package.json @@ -11,9 +11,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "terser": "^3.17.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/shader-ast": "latest", diff --git a/examples/shader-ast-raymarch/src/index.ts b/examples/shader-ast-raymarch/src/index.ts index 23e9bdcc73..01f896bdcf 100644 --- a/examples/shader-ast-raymarch/src/index.ts +++ b/examples/shader-ast-raymarch/src/index.ts @@ -21,7 +21,7 @@ import { vec4 } from "@thi.ng/shader-ast"; import { GLSLVersion, targetGLSL } from "@thi.ng/shader-ast-glsl"; -import { initRuntime, targetJS } from "@thi.ng/shader-ast-js"; +import { canvasRenderer, targetJS } from "@thi.ng/shader-ast-js"; import { clamp01, diffuseLighting, @@ -183,7 +183,7 @@ if (JS_MODE) { // JS Canvas 2D shader emulation from here... // const fn = JS.compile(shaderProgram).mainImage; - const rt = initRuntime({ canvas }); + const rt = canvasRenderer(canvas); let time = 0; setInterval(() => { @@ -193,7 +193,7 @@ if (JS_MODE) { Math.cos(time / 2) * 0.7, Math.sin(time) * 2.5 ]; - rt.update((frag) => fn(frag, size, eyePos, lightDir)); + rt((frag) => fn(frag, size, eyePos, lightDir)); }, 16); } else { // diff --git a/examples/shader-ast-sdf2d/index.html b/examples/shader-ast-sdf2d/index.html index bf6558aac0..b1bb4735af 100644 --- a/examples/shader-ast-sdf2d/index.html +++ b/examples/shader-ast-sdf2d/index.html @@ -27,7 +27,7 @@

Source code

diff --git a/examples/shader-ast-sdf2d/package.json b/examples/shader-ast-sdf2d/package.json index ee609cee03..ca086f530b 100644 --- a/examples/shader-ast-sdf2d/package.json +++ b/examples/shader-ast-sdf2d/package.json @@ -7,12 +7,13 @@ "scripts": { "clean": "rm -rf .cache build out", "build": "yarn clean && parcel build index.html -d out --public-url ./ --no-source-maps --no-cache --detailed-report", + "build:webpack": "../../node_modules/.bin/webpack --mode production", "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "terser": "^3.17.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/shader-ast": "latest", diff --git a/examples/shader-ast-sdf2d/src/index.ts b/examples/shader-ast-sdf2d/src/index.ts index 9e7c85bd00..b22191c7ed 100644 --- a/examples/shader-ast-sdf2d/src/index.ts +++ b/examples/shader-ast-sdf2d/src/index.ts @@ -17,7 +17,7 @@ import { vec4 } from "@thi.ng/shader-ast"; import { GLSLVersion, targetGLSL } from "@thi.ng/shader-ast-glsl"; -import { initRuntime, targetJS } from "@thi.ng/shader-ast-js"; +import { canvasRenderer, targetJS } from "@thi.ng/shader-ast-js"; import { aspectCorrectedUV, fit1101, @@ -114,10 +114,10 @@ if (JS_MODE) { // JS Canvas 2D shader emulation from here... // const fn = JS.compile(shaderProgram).mainImage; - const rt = initRuntime({ canvas }); + const rt = canvasRenderer(canvas); setInterval(() => { - rt.update((frag) => fn(frag, size)); + rt((frag) => fn(frag, size)); }, 16); } else { // diff --git a/examples/shader-ast-sdf2d/webpack.config.js b/examples/shader-ast-sdf2d/webpack.config.js new file mode 100644 index 0000000000..bf16021356 --- /dev/null +++ b/examples/shader-ast-sdf2d/webpack.config.js @@ -0,0 +1,23 @@ +module.exports = { + entry: "./src/index.ts", + output: { + filename: "bundle.[hash].js", + path: __dirname + "/out" + }, + resolve: { + extensions: [".ts", ".js"] + }, + module: { + rules: [ + { + test: /\.(png|jpg|gif)$/, + loader: "file-loader", + options: { name: "[path][hash].[ext]" } + }, + { test: /\.ts$/, use: "ts-loader" } + ] + }, + node: { + process: false + } +}; diff --git a/examples/shader-ast-tunnel/index.html b/examples/shader-ast-tunnel/index.html index c0b8c1f0e7..74e1fac015 100644 --- a/examples/shader-ast-tunnel/index.html +++ b/examples/shader-ast-tunnel/index.html @@ -27,7 +27,7 @@

Source code

diff --git a/examples/shader-ast-tunnel/package.json b/examples/shader-ast-tunnel/package.json index bb0d0ea3c0..d16cf70815 100644 --- a/examples/shader-ast-tunnel/package.json +++ b/examples/shader-ast-tunnel/package.json @@ -7,12 +7,13 @@ "scripts": { "clean": "rm -rf .cache build out", "build": "yarn clean && parcel build index.html -d out --public-url ./ --no-source-maps --no-cache --detailed-report", + "build:webpack": "../../node_modules/.bin/webpack --mode production", "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "terser": "^3.17.0", - "typescript": "^3.5.3" + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/binary": "latest", diff --git a/examples/shader-ast-tunnel/src/index.ts b/examples/shader-ast-tunnel/src/index.ts index b9618d4510..e05597f4b1 100644 --- a/examples/shader-ast-tunnel/src/index.ts +++ b/examples/shader-ast-tunnel/src/index.ts @@ -25,7 +25,7 @@ import { vec4 } from "@thi.ng/shader-ast"; import { GLSLVersion, targetGLSL } from "@thi.ng/shader-ast-glsl"; -import { initRuntime, JS_DEFAULT_ENV, targetJS } from "@thi.ng/shader-ast-js"; +import { canvasRenderer, JS_DEFAULT_ENV, targetJS } from "@thi.ng/shader-ast-js"; import { compileModel, draw, @@ -129,12 +129,12 @@ if (JS_MODE) { // under the hood all vector & matrix operations delegate to // thi.ng/vectors and thi.ng/matrices packages by default const fn = JS.compile(shaderProgram).mainImage; - const rt = initRuntime({ canvas }); + const rt = canvasRenderer(canvas); let time = 0; setInterval(() => { time += 0.05; - rt.update((frag) => fn(frag, size, time)); + rt((frag) => fn(frag, size, time)); }, 16); }); } else { diff --git a/examples/shader-ast-tunnel/src/webpack.d.ts b/examples/shader-ast-tunnel/src/webpack.d.ts new file mode 100644 index 0000000000..6e39ca7616 --- /dev/null +++ b/examples/shader-ast-tunnel/src/webpack.d.ts @@ -0,0 +1,3 @@ +declare module "*.jpg"; +declare module "*.png"; +declare module "*.svg"; diff --git a/examples/shader-ast-tunnel/webpack.config.js b/examples/shader-ast-tunnel/webpack.config.js new file mode 100644 index 0000000000..bf16021356 --- /dev/null +++ b/examples/shader-ast-tunnel/webpack.config.js @@ -0,0 +1,23 @@ +module.exports = { + entry: "./src/index.ts", + output: { + filename: "bundle.[hash].js", + path: __dirname + "/out" + }, + resolve: { + extensions: [".ts", ".js"] + }, + module: { + rules: [ + { + test: /\.(png|jpg|gif)$/, + loader: "file-loader", + options: { name: "[path][hash].[ext]" } + }, + { test: /\.ts$/, use: "ts-loader" } + ] + }, + node: { + process: false + } +}; diff --git a/examples/soa-ecs/.gitignore b/examples/soa-ecs/.gitignore new file mode 100644 index 0000000000..0c5abcab62 --- /dev/null +++ b/examples/soa-ecs/.gitignore @@ -0,0 +1,5 @@ +.cache +out +node_modules +yarn.lock +*.js diff --git a/examples/soa-ecs/README.md b/examples/soa-ecs/README.md new file mode 100644 index 0000000000..1581428cd9 --- /dev/null +++ b/examples/soa-ecs/README.md @@ -0,0 +1,15 @@ +# soa-ecs + +![100k particle system](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/soa-ecs-100k.png) + +[Live demo](http://demo.thi.ng/umbrella/soa-ecs/) + +Please refer to the [example build instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) on the wiki. + +## Authors + +- Karsten Schmidt + +## License + +© 2019 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/soa-ecs/index.html b/examples/soa-ecs/index.html new file mode 100644 index 0000000000..a227d34712 --- /dev/null +++ b/examples/soa-ecs/index.html @@ -0,0 +1,26 @@ + + + + + + + soa-ecs + + + + + +
+ + + + diff --git a/examples/soa-ecs/package.json b/examples/soa-ecs/package.json new file mode 100644 index 0000000000..563ac13e95 --- /dev/null +++ b/examples/soa-ecs/package.json @@ -0,0 +1,35 @@ +{ + "name": "soa-ecs", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "clean": "rm -rf .cache build out", + "build": "yarn clean && parcel build index.html -d out --public-url ./ --no-source-maps --no-cache --detailed-report --experimental-scope-hoisting", + "build:webpack": "../../node_modules/.bin/webpack --mode production", + "start": "parcel index.html -p 8080 --open" + }, + "devDependencies": { + "parcel-bundler": "^1.12.3", + "terser": "^3.17.0", + "typescript": "^3.4.1" + }, + "dependencies": { + "@thi.ng/api": "latest", + "@thi.ng/ecs": "latest", + "@thi.ng/hdom": "latest", + "@thi.ng/hdom-components": "latest", + "@thi.ng/math": "latest", + "@thi.ng/matrices": "latest", + "@thi.ng/shader-ast": "latest", + "@thi.ng/vectors": "latest", + "@thi.ng/webgl": "latest" + }, + "browserslist": [ + "last 3 Chrome versions" + ], + "browser": { + "process": false + } +} \ No newline at end of file diff --git a/examples/soa-ecs/src/index.ts b/examples/soa-ecs/src/index.ts new file mode 100644 index 0000000000..085af60e30 --- /dev/null +++ b/examples/soa-ecs/src/index.ts @@ -0,0 +1,225 @@ +import { Type } from "@thi.ng/api"; +import { ECS, GroupInfo, GroupTuple } from "@thi.ng/ecs"; +import { start } from "@thi.ng/hdom"; +import { adaptDPI, canvasWebGL } from "@thi.ng/hdom-components"; +import { fract } from "@thi.ng/math"; +import { ortho } from "@thi.ng/matrices"; +import { + $y, + assign, + defMain, + float, + Mat4Sym, + mix, + mul, + Vec2Sym, + vec4 +} from "@thi.ng/shader-ast"; +import { + add2, + addS2, + magSq2, + magSqS2, + mixN2, + mixNS2, + mulN2, + mulNS2, + normalize, + normalizeS2, + randNorm, + randNormS2, + rotate, + rotateS2, + setVN4 +} from "@thi.ng/vectors"; +import { + BLEND_ADD, + compileModel, + draw, + GLMat4, + GLVec4, + ModelSpec, + shader, + ShaderSpec +} from "@thi.ng/webgl"; + +const BATCH_UPDATE = true; + +const NUM = 100000; +const W = 480; +const W2 = W / 2; + +const ALPHA = 0.3; +const COLOR = [1, 0.7, 0.1, 0.001]; +const COLOR2 = [0.1, 0.9, 1, 0.001]; + +// component type mapping used to configure ECS and all of its derived types +interface CompSpecs { + pos: Float32Array; + vel: Float32Array; +} + +const ecs = new ECS(NUM); + +const pos = ecs.defComponent({ + id: "pos", + type: Type.F32, + size: 2 +}); + +const vel = ecs.defComponent({ + id: "vel", + type: Type.F32, + size: 2, + default: () => randNormS2([0, 0]) +}); + +const group = ecs.defGroup([pos, vel]); + +for (let i = 0; i < NUM; i++) { + ecs.defEntity([pos, vel]); +} + +const dir = [0, 0]; + +// batch version of `updateSingle` below... +// uses strided vector ops to update the flat component buffers +// on my MBP2015 this is about 1.5 - 2x faster +const updateBatch = ( + info: GroupInfo, + num: number, + t: number, + amp: number +) => { + const { values: pos, stride: ps } = info.pos; + const { values: vel, stride: vs } = info.vel; + const invNum = 1 / num; + for (let i = 0; i < num; i++) { + const ip = i * ps; + const iv = i * vs; + const m = magSqS2(pos, ip); + rotateS2(pos, pos, m * amp, ip, ip); + if (m < 4e4) { + mixNS2(vel, vel, dir, 0.01 + 0.2 * fract((i + t) * invNum), iv, iv); + normalizeS2(vel, vel, 1, iv, iv); + } else { + mulNS2(pos, pos, 0.98, ip, ip); + randNormS2(vel, 1, undefined, iv); + } + addS2(pos, pos, vel, ip, ip, iv); + } +}; + +const updateSingle = ( + { pos, vel }: GroupTuple, + i: number, + t: number, + amp: number +) => { + const m = magSq2(pos); + rotate(pos, pos, m * amp); + if (m < 4e4) { + normalize(vel, mixN2(vel, vel, dir, 0.01 + 0.2 * fract((i + t) / NUM))); + } else { + mulN2(pos, pos, 0.98); + randNorm(vel); + } + add2(pos, pos, vel); +}; + +const pointShader: ShaderSpec = { + vs: (gl, unis, ins, outs) => [ + defMain(() => [ + assign( + outs.vcol, + mix( + unis.color, + unis.color2, + mul(0.006, $y(ins.position)) + ) + ), + assign( + gl.gl_Position, + mul(unis.proj, vec4(ins.position, 0, 1)) + ), + assign(gl.gl_PointSize, float(2)) + ]) + ], + fs: (gl, unis, ins, outs) => [ + defMain(() => [assign(outs.fragColor, ins.vcol)]) + ], + attribs: { + position: "vec2" + }, + varying: { + vcol: "vec4" + }, + uniforms: { + proj: "mat4", + color: "vec4", + color2: "vec4" + }, + state: { + depth: false, + blend: true, + blendFn: BLEND_ADD + } +}; + +const app = () => { + let model: ModelSpec; + let targetDir = [0, 0]; + const canvas = canvasWebGL({ + init: (_, gl) => { + model = compileModel(gl, { + attribs: { + position: { + data: pos.packedValues(), + size: 2 + } + }, + shader: shader(gl, pointShader), + uniforms: { + proj: ortho([], -W2, W2, -W2, W2, 0, 1), + color: COLOR, + color2: COLOR2 + }, + num: NUM, + mode: gl.POINTS + }); + }, + update: (el, gl, __, time) => { + // nothing to be done in first frame + if (!model) { + adaptDPI(el, W, W); + return; + } + time *= 0.001; + mixN2(dir, dir, randNormS2(targetDir), 0.1); + + // animate particles and update WebGL buffer + BATCH_UPDATE + ? group.run(updateBatch, time, Math.sin(time / 8) * 4e-7) + : group.forEach(updateSingle, time, Math.sin(time / 8) * 4e-7); + model.attribs.position.buffer!.set(model.attribs.position.data!); + + const alpha = Math.pow(Math.min(time / 5, 1), 3) * ALPHA; + if (alpha < 1) { + const col1 = model.uniforms!.color; + const col2 = model.uniforms!.color2; + setVN4(col1, col1, alpha); + setVN4(col2, col2, alpha); + } + + gl.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + draw(model); + } + }); + return () => [ + "div.bg-black.vh-100.flex.flex-column.items-center.justify-center", + [canvas, { width: W, height: W }] + ]; +}; + +start(app()); diff --git a/examples/soa-ecs/tsconfig.json b/examples/soa-ecs/tsconfig.json new file mode 100644 index 0000000000..3409545b1e --- /dev/null +++ b/examples/soa-ecs/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "target": "es6", + "sourceMap": true, + "noUnusedLocals": false, + "noUnusedParameters": false + }, + "include": ["./src/**/*.ts"] +} diff --git a/examples/svg-barchart/README.md b/examples/svg-barchart/README.md index 2c0026dddf..fa7de9ffbb 100644 --- a/examples/svg-barchart/README.md +++ b/examples/svg-barchart/README.md @@ -4,7 +4,7 @@ SVG bar chart component & one-off rendering. -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/svg-barchart.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/svg-barchart.png) Please refer to the [example build instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) diff --git a/examples/svg-barchart/package.json b/examples/svg-barchart/package.json index 1c34f68ab7..3eeb746170 100644 --- a/examples/svg-barchart/package.json +++ b/examples/svg-barchart/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest", diff --git a/examples/svg-particles/package.json b/examples/svg-particles/package.json index 4ed33a464e..ddd003f724 100644 --- a/examples/svg-particles/package.json +++ b/examples/svg-particles/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest", diff --git a/examples/svg-waveform/package.json b/examples/svg-waveform/package.json index 36e9930c98..475c3b24fd 100644 --- a/examples/svg-waveform/package.json +++ b/examples/svg-waveform/package.json @@ -21,9 +21,9 @@ }, "devDependencies": { "@types/node": "^9.6.2", - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "browserslist": [ "last 3 Chrome versions" diff --git a/examples/talk-slides/package.json b/examples/talk-slides/package.json index 705cb11604..f2d3f81622 100644 --- a/examples/talk-slides/package.json +++ b/examples/talk-slides/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest", diff --git a/examples/texgen/src/webpack.d.ts b/examples/texgen/src/webpack.d.ts new file mode 100644 index 0000000000..6e39ca7616 --- /dev/null +++ b/examples/texgen/src/webpack.d.ts @@ -0,0 +1,3 @@ +declare module "*.jpg"; +declare module "*.png"; +declare module "*.svg"; diff --git a/examples/todo-list/package.json b/examples/todo-list/package.json index f5e0b702af..1ba797434a 100644 --- a/examples/todo-list/package.json +++ b/examples/todo-list/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/transducers-hdom/package.json b/examples/transducers-hdom/package.json index 3b4dc0a34d..6fb09d4465 100644 --- a/examples/transducers-hdom/package.json +++ b/examples/transducers-hdom/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/rstream": "latest", diff --git a/examples/triple-query/package.json b/examples/triple-query/package.json index 5217a76aff..33d628cab3 100644 --- a/examples/triple-query/package.json +++ b/examples/triple-query/package.json @@ -10,9 +10,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/webgl-cubemap/package.json b/examples/webgl-cubemap/package.json index 8f50fe323e..0ef560e4ce 100644 --- a/examples/webgl-cubemap/package.json +++ b/examples/webgl-cubemap/package.json @@ -11,9 +11,9 @@ "start": "cp -R assets dist/ && parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/dsp": "latest", diff --git a/examples/webgl-cubemap/src/index.ts b/examples/webgl-cubemap/src/index.ts index 0aee8401bc..3d17f2229d 100644 --- a/examples/webgl-cubemap/src/index.ts +++ b/examples/webgl-cubemap/src/index.ts @@ -17,6 +17,7 @@ import { vec4 } from "@thi.ng/shader-ast"; import { + BLEND_ADD, compileModel, cube, cubeMap, @@ -50,7 +51,9 @@ const CUBEMAP_SHADER: ShaderSpec = { tex: "samplerCube" }, state: { - depth: true + depth: false, + blend: true, + blendFn: BLEND_ADD } }; @@ -101,8 +104,8 @@ const app = () => { // prettier-ignore update: (el, gl, __, time) => { if (!model) return; - const bg = 0.1; - const p = perspective([], 45, gl.drawingBufferWidth/gl.drawingBufferHeight, 0.01, 5); + const bg = 0.01; + const p = perspective([], 45, gl.drawingBufferWidth/gl.drawingBufferHeight, 0.01, 10); const v = lookAt([],[0, 0, sin(time, 0.00008, 1.99, 2)],[0, 0, 0], [0, 1, 0]); const m = transform44([], [0, 0, 0], [sin(time, 0.0001, 0.7, 0.5), time * 0.0007,0], 1); model.uniforms!.mvp = concat([], p, v, m); diff --git a/examples/webgl-gpgpu-basics/README.md b/examples/webgl-gpgpu-basics/README.md deleted file mode 100644 index 24afc550c6..0000000000 --- a/examples/webgl-gpgpu-basics/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# webgl-gpgpu-basics - -[Live demo](http://demo.thi.ng/umbrella/webgl-gpgpu-basics/) - -Minimal example demonstrating usage of -[@thi.ng/webgl](https://github.com/thi-ng/umbrella/tree/master/packages/webgl)'s -GPGPU capability. Additionally, the actual GPU code is written directly -in TypeScript, using -[@thi.ng/shader-ast](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast). - -Please refer to the [example build instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) on the wiki. - -## Authors - -- Karsten Schmidt - -## License - -© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/webgl-gpgpu-basics/src/index.ts b/examples/webgl-gpgpu-basics/src/index.ts deleted file mode 100644 index db9c24c869..0000000000 --- a/examples/webgl-gpgpu-basics/src/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - assign, - defMain, - index, - mul, - sym, - texture, - Vec4Sym -} from "@thi.ng/shader-ast"; -import { range } from "@thi.ng/transducers"; -import { gpgpu } from "@thi.ng/webgl"; - -const ctx = gpgpu({ - size: 64, // might be adjusted / rounded up to next power of 2 - inputs: 1, // max inputs - outputs: 3, // max outputs - version: 2 // webgl version -}); - -const job = ctx.newJob({ - inputs: 1, - outputs: 2, - src: (_, unis, ins, outs) => [ - defMain(() => { - let val: Vec4Sym; - return [ - (val = sym(texture(index(unis.inputs, 0), ins.v_uv))), - assign(outs.output0, mul(val, unis.factorA)), - assign(outs.output1, mul(val, unis.factorB)) - ]; - }) - ], - uniforms: { - // uniform params incl. default values - // an `inputs` uniform (sampler2D[]) will be injected automatically - factorA: ["float", 2], - factorB: ["float", 3] - } -}); - -const src = new Float32Array([...range(ctx.inputSize(0))]); -console.log("Original input data:"); -console.log(src); - -job.run({ inputs: [src] }); -console.log("Results of first iteration:"); -// obtain results as Float32Array -console.log(job.result(null, 0)); -console.log(job.result(null, 1)); - -job.run({ inputs: [ctx.outputs[0].tex], outputs: [1, 2] }); -console.log("Results of second iteration:"); -console.log(job.result(null, 1)); -console.log(job.result(null, 2)); - -job.run({ - inputs: [ctx.outputs[1].tex], - outputs: [0, 2], - uniforms: { factorA: 10, factorB: 100 } -}); -console.log("Final results:"); -console.log(job.result(null, 0)); -console.log(job.result(null, 2)); diff --git a/examples/webgl-grid/package.json b/examples/webgl-grid/package.json index 2bc93c6e7c..02592f0c8b 100644 --- a/examples/webgl-grid/package.json +++ b/examples/webgl-grid/package.json @@ -11,10 +11,10 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "rimraf": "^2.6.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/hdom": "latest", diff --git a/examples/webgl-msdf/package.json b/examples/webgl-msdf/package.json index 791ba78b20..04f21a464e 100644 --- a/examples/webgl-msdf/package.json +++ b/examples/webgl-msdf/package.json @@ -11,9 +11,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/webgl-multipass/.gitignore b/examples/webgl-multipass/.gitignore new file mode 100644 index 0000000000..0c5abcab62 --- /dev/null +++ b/examples/webgl-multipass/.gitignore @@ -0,0 +1,5 @@ +.cache +out +node_modules +yarn.lock +*.js diff --git a/examples/webgl-multipass/README.md b/examples/webgl-multipass/README.md new file mode 100644 index 0000000000..d9da83a2d2 --- /dev/null +++ b/examples/webgl-multipass/README.md @@ -0,0 +1,13 @@ +# webgl-multipass + +[Live demo](http://demo.thi.ng/umbrella/webgl-multipass/) + +Please refer to the [example build instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) on the wiki. + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/webgl-multipass/index.html b/examples/webgl-multipass/index.html new file mode 100644 index 0000000000..4746f103f8 --- /dev/null +++ b/examples/webgl-multipass/index.html @@ -0,0 +1,16 @@ + + + + + + + webgl-multipass + + + + +
+ + + diff --git a/examples/webgl-multipass/package.json b/examples/webgl-multipass/package.json new file mode 100644 index 0000000000..5e6b9a4bc3 --- /dev/null +++ b/examples/webgl-multipass/package.json @@ -0,0 +1,29 @@ +{ + "name": "webgl-multipass", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "clean": "rm -rf .cache build out", + "build": "yarn clean && parcel build index.html -d out --public-url ./ --no-source-maps --no-cache --detailed-report --experimental-scope-hoisting", + "build:webpack": "../../node_modules/.bin/webpack --mode production", + "start": "parcel index.html -p 8080 --open" + }, + "devDependencies": { + "parcel-bundler": "^1.12.4", + "terser": "^3.17.0", + "typescript": "^3.6.4" + }, + "dependencies": { + "@thi.ng/api": "latest", + "@thi.ng/rstream": "latest", + "@thi.ng/transducers-hdom": "latest" + }, + "browserslist": [ + "last 3 Chrome versions" + ], + "browser": { + "process": false + } +} \ No newline at end of file diff --git a/examples/webgl-multipass/src/index.ts b/examples/webgl-multipass/src/index.ts new file mode 100644 index 0000000000..ea9c2e4945 --- /dev/null +++ b/examples/webgl-multipass/src/index.ts @@ -0,0 +1,131 @@ +import { canvas2d, GRAY8, PackedBuffer } from "@thi.ng/pixel"; +import { + $x, + $xyz, + assign, + defMain, + fract, + index, + length, + mul, + pow, + sin, + sub, + sym, + texture, + Vec2Sym, + vec3, + vec4, + Vec4Sym +} from "@thi.ng/shader-ast"; +import { clamp01, fit1101, fragUV } from "@thi.ng/shader-ast-stdlib"; +import { + glCanvas, + multipass, + readTexture, + TextureFormat, + TextureType +} from "@thi.ng/webgl"; + +// create WebGL canvas +const canvas = glCanvas({ + // width: window.innerWidth, + // height: window.innerHeight, + width: 512, + height: 512, + parent: document.body, + version: 2 +}); + +// init multipass shader pipeline +const toy = multipass({ + gl: canvas.gl, + width: 32, + height: 32, + textures: { + foo: { format: TextureFormat.RGBA32F }, + bar: { format: TextureFormat.R32F } + }, + passes: [ + { + fs: (gl, unis, _, outs) => [ + defMain(() => { + let uv: Vec2Sym; + return [ + (uv = sym(fragUV(gl.gl_FragCoord, unis.resolution))), + assign( + outs.output0, + vec4(vec3(uv, fract(unis.time)), 1) + ), + assign( + outs.output1, + vec4(clamp01(length(sub(uv, 0.5)))) + ) + ]; + }) + ], + inputs: [], + outputs: ["foo", "bar"], + uniforms: { + time: "float", + resolution: "vec2" + }, + uniformVals: { + // foo: () => Math.random() + } + }, + { + fs: (gl, unis, _, outs) => [ + defMain(() => { + let uv: Vec2Sym; + return [ + (uv = sym(fragUV(gl.gl_FragCoord, unis.resolution))), + assign( + outs.fragColor, + vec4( + mul( + $xyz(texture(index(unis.inputs, 0), uv)), + pow( + $x(texture(index(unis.inputs, 1), uv)), + fit1101(sin(unis.time)) + ) + ), + 1 + ) + ) + ]; + }) + ], + inputs: ["foo", "bar"], + outputs: [], + uniforms: { + resolution: "vec2", + time: "float" + } + } + ] +}); + +toy.update(0); + +const canv = canvas2d(32, 32); +document.body.appendChild(canv.canvas); +new PackedBuffer( + 32, + 32, + GRAY8, + readTexture( + canvas.gl, + toy.textures.bar, + TextureFormat.RED, + TextureType.UNSIGNED_BYTE, + new Uint8Array(32 * 32) + ) +).blitCanvas(canv.canvas); + +toy.start(); + +if (process.env.NODE_ENV !== "production") { + const hot = (module).hot; + hot && hot.dispose(() => toy.stop()); +} diff --git a/examples/webgl-multipass/tsconfig.json b/examples/webgl-multipass/tsconfig.json new file mode 100644 index 0000000000..bbf112cc18 --- /dev/null +++ b/examples/webgl-multipass/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "target": "es6", + "sourceMap": true + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/examples/webgl-multipass/webpack.config.js b/examples/webgl-multipass/webpack.config.js new file mode 100644 index 0000000000..bf16021356 --- /dev/null +++ b/examples/webgl-multipass/webpack.config.js @@ -0,0 +1,23 @@ +module.exports = { + entry: "./src/index.ts", + output: { + filename: "bundle.[hash].js", + path: __dirname + "/out" + }, + resolve: { + extensions: [".ts", ".js"] + }, + module: { + rules: [ + { + test: /\.(png|jpg|gif)$/, + loader: "file-loader", + options: { name: "[path][hash].[ext]" } + }, + { test: /\.ts$/, use: "ts-loader" } + ] + }, + node: { + process: false + } +}; diff --git a/examples/webgl-shadertoy/.gitignore b/examples/webgl-shadertoy/.gitignore new file mode 100644 index 0000000000..0c5abcab62 --- /dev/null +++ b/examples/webgl-shadertoy/.gitignore @@ -0,0 +1,5 @@ +.cache +out +node_modules +yarn.lock +*.js diff --git a/examples/webgl-shadertoy/README.md b/examples/webgl-shadertoy/README.md new file mode 100644 index 0000000000..014b01d782 --- /dev/null +++ b/examples/webgl-shadertoy/README.md @@ -0,0 +1,13 @@ +# webgl-shadertoy + +[Live demo](http://demo.thi.ng/umbrella/webgl-shadertoy/) + +Please refer to the [example build instructions](https://github.com/thi-ng/umbrella/wiki/Example-build-instructions) on the wiki. + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/webgl-shadertoy/index.html b/examples/webgl-shadertoy/index.html new file mode 100644 index 0000000000..6bc7487bee --- /dev/null +++ b/examples/webgl-shadertoy/index.html @@ -0,0 +1,24 @@ + + + + + + + webgl-shadertoy + + + + +
+ Hold down mouse button to change color theme +
+ + + diff --git a/examples/webgl-shadertoy/package.json b/examples/webgl-shadertoy/package.json new file mode 100644 index 0000000000..3e230e7387 --- /dev/null +++ b/examples/webgl-shadertoy/package.json @@ -0,0 +1,29 @@ +{ + "name": "webgl-shadertoy", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "clean": "rm -rf .cache build out", + "build": "yarn clean && parcel build index.html -d out --public-url ./ --no-source-maps --no-cache --detailed-report --experimental-scope-hoisting", + "build:webpack": "../../node_modules/.bin/webpack --mode production", + "start": "parcel index.html -p 8080 --open" + }, + "devDependencies": { + "parcel-bundler": "^1.12.4", + "terser": "^3.17.0", + "typescript": "^3.4.1" + }, + "dependencies": { + "@thi.ng/api": "latest", + "@thi.ng/rstream": "latest", + "@thi.ng/transducers-hdom": "latest" + }, + "browserslist": [ + "last 3 Chrome versions" + ], + "browser": { + "process": false + } +} \ No newline at end of file diff --git a/examples/webgl-shadertoy/src/index.ts b/examples/webgl-shadertoy/src/index.ts new file mode 100644 index 0000000000..f5953359ca --- /dev/null +++ b/examples/webgl-shadertoy/src/index.ts @@ -0,0 +1,88 @@ +import { + $xy, + add, + distance, + eq, + float, + FloatSym, + fract, + int, + min, + mix, + mul, + neg, + ret, + sin, + sym, + Vec2Sym, + Vec2Term, + vec3, + Vec3Sym, + vec4 +} from "@thi.ng/shader-ast"; +import { aspectCorrectedUV, fit1101 } from "@thi.ng/shader-ast-stdlib"; +import { glCanvas } from "@thi.ng/webgl"; +import { MainImageFn, shaderToy } from "@thi.ng/webgl-shadertoy"; + +// main shader function, supplied to `shaderToy` below +// the 2 args given are objects containing GLSL builtin vars and uniforms +// +// see: +// https://github.com/thi-ng/umbrella/blob/master/packages/shader-ast-glsl/src/api.ts#L22 +// https://github.com/thi-ng/umbrella/blob/master/packages/webgl-shadertoy/src/api.ts#L13 +const mainImage: MainImageFn = (gl, unis) => { + // predeclare local vars / symbols + let uv: Vec2Sym; + let mp: Vec2Sym; + let d1: FloatSym; + let d2: FloatSym; + let col: Vec3Sym; + + // Inline function to create ring pattern with center at `p` + const rings = (p: Vec2Term, speed = 0.25, freq = 50) => + sin(mul(add(distance(uv, p), fract(mul(unis.time, speed))), freq)); + + return [ + // let's work in [-1..+1] range (based on vertical resolution) + (uv = sym(aspectCorrectedUV($xy(gl.gl_FragCoord), unis.resolution))), + (mp = sym(aspectCorrectedUV(unis.mouse, unis.resolution))), + // compute ring colors + (d1 = sym(rings(mp))), + (d2 = sym(rings(neg(mp)))), + // combine rings and multiply with target color based on + // mouse button state + (col = sym( + mul( + vec3(fit1101(min(d1, d2))), + mix( + vec3(1), + vec3(d1, 0, d2), + float(eq(unis.mouseButtons, int(1))) + ) + ) + )), + // return as vec4 (mandatory) + ret(vec4(col, 1)) + ]; +}; + +// create WebGL canvas +const canvas = glCanvas({ + width: window.innerWidth, + height: window.innerHeight, + parent: document.body, + version: 1 +}); + +// init shader toy with canvas & shader fn +const toy = shaderToy({ + canvas: canvas.canvas, + gl: canvas.gl, + main: mainImage +}); + +toy.start(); + +// toy.stop(); + +// toy.recompile(/* pass new mainImage fn */); diff --git a/examples/webgl-shadertoy/tsconfig.json b/examples/webgl-shadertoy/tsconfig.json new file mode 100644 index 0000000000..bbf112cc18 --- /dev/null +++ b/examples/webgl-shadertoy/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "target": "es6", + "sourceMap": true + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/examples/webgl-shadertoy/webpack.config.js b/examples/webgl-shadertoy/webpack.config.js new file mode 100644 index 0000000000..bf16021356 --- /dev/null +++ b/examples/webgl-shadertoy/webpack.config.js @@ -0,0 +1,23 @@ +module.exports = { + entry: "./src/index.ts", + output: { + filename: "bundle.[hash].js", + path: __dirname + "/out" + }, + resolve: { + extensions: [".ts", ".js"] + }, + module: { + rules: [ + { + test: /\.(png|jpg|gif)$/, + loader: "file-loader", + options: { name: "[path][hash].[ext]" } + }, + { test: /\.ts$/, use: "ts-loader" } + ] + }, + node: { + process: false + } +}; diff --git a/examples/webgl-ssao/package.json b/examples/webgl-ssao/package.json index 870322117e..5b98288ed7 100644 --- a/examples/webgl-ssao/package.json +++ b/examples/webgl-ssao/package.json @@ -11,9 +11,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/webgl-ssao/src/index.ts b/examples/webgl-ssao/src/index.ts index 70e6aa951e..fc77928e7d 100644 --- a/examples/webgl-ssao/src/index.ts +++ b/examples/webgl-ssao/src/index.ts @@ -72,13 +72,11 @@ const app = () => { } const [colorTex, posTex, normTex, noiseTex, ssaoTex] = [ {}, - { internalFormat: gl.RGBA16F, type: gl.FLOAT }, - { internalFormat: gl.RGBA16F, type: gl.FLOAT }, + { format: gl.RGBA16F }, + { format: gl.RGBA16F }, { image: NOISE, - internalFormat: gl.RG16F, - format: gl.RG, - type: gl.FLOAT + format: gl.RG16F }, {} ].map((opts: Partial) => @@ -206,5 +204,3 @@ if (process.env.NODE_ENV !== "production") { const hot = (module).hot; hot && hot.dispose(cancel); } - -//window["params"] = PARAMS; diff --git a/examples/webgl-ssao/src/params.ts b/examples/webgl-ssao/src/params.ts index 5212cc7b6e..ab76286d38 100644 --- a/examples/webgl-ssao/src/params.ts +++ b/examples/webgl-ssao/src/params.ts @@ -24,8 +24,10 @@ const slider = (label: string, attribs: any, stream: Stream) => () => [ ["span.ml3", stream.deref()] ]; +type ParamDef = [string, any, number]; + // prettier-ignore -export const PARAM_DEFS: IObjectOf<[string, any, number]> = { +export const PARAM_DEFS: IObjectOf = { radius: ["radius", { min: 2, max: 64, step: 1 }, 32], bias: ["bias", { min: -0.2, max: 0.2, step: 0.01 }, 0.09], baseAttenuation: ["base attenuation", { min: 0.1, max: 2, step: 0.01 }, 1], @@ -37,12 +39,12 @@ export const PARAM_DEFS: IObjectOf<[string, any, number]> = { }; export const PARAMS = transduce( - map(([id, spec]) => { + map<[string, ParamDef], [string, Stream]>(([id, spec]) => { const param = stream(); param.next(spec[2]); return [id, param]; }), - assocObj>(), + assocObj(), pairs(PARAM_DEFS) ); diff --git a/examples/webgl-ssao/src/shaders.ts b/examples/webgl-ssao/src/shaders.ts index bb52662115..28011494c0 100644 --- a/examples/webgl-ssao/src/shaders.ts +++ b/examples/webgl-ssao/src/shaders.ts @@ -10,7 +10,12 @@ import { vec4 } from "@thi.ng/shader-ast"; import { clamp01 } from "@thi.ng/shader-ast-stdlib"; -import { FX_SHADER_SPEC, ShaderFn, ShaderSpec } from "@thi.ng/webgl"; +import { + FX_SHADER_SPEC, + FX_SHADER_SPEC_UV, + ShaderFn, + ShaderSpec +} from "@thi.ng/webgl"; export const LIGHT_SHADER: ShaderSpec = { vs: `void main() { @@ -119,7 +124,7 @@ void main() { } }; -export const FINAL_SHADER: ShaderSpec = mergeDeepObj(FX_SHADER_SPEC, { +export const FINAL_SHADER: ShaderSpec = mergeDeepObj(FX_SHADER_SPEC_UV, { fs: ( ((_, unis, ins, outs) => [ defMain(() => [ diff --git a/examples/wolfram/package.json b/examples/wolfram/package.json index c5b9a07272..a1b2f94bf5 100644 --- a/examples/wolfram/package.json +++ b/examples/wolfram/package.json @@ -10,10 +10,10 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "rimraf": "^2.6.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/api": "latest", diff --git a/examples/xml-converter/README.md b/examples/xml-converter/README.md index d60ae94e3e..12e58de0a8 100644 --- a/examples/xml-converter/README.md +++ b/examples/xml-converter/README.md @@ -15,7 +15,7 @@ This diagram illustrates the [@thi.ng/rstream](https://github.com/thi-ng/umbrella/tree/master/packages/rstream) dataflow topology used by the browser app: -![dataflow](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/xml-converter.png) +![dataflow](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/xml-converter.png) ## Browser version diff --git a/examples/xml-converter/package.json b/examples/xml-converter/package.json index d8823c93b7..dd661481cf 100644 --- a/examples/xml-converter/package.json +++ b/examples/xml-converter/package.json @@ -11,9 +11,9 @@ "start": "parcel index.html -p 8080 --open" }, "devDependencies": { - "parcel-bundler": "^1.12.3", - "terser": "^4.0.0", - "typescript": "^3.5.3" + "parcel-bundler": "^1.12.4", + "terser": "^4.3.8", + "typescript": "^3.6.4" }, "dependencies": { "@thi.ng/arrays": "latest", diff --git a/package.json b/package.json index deaa3bc5fb..ac2dc01b34 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,14 @@ "lodash.template": "^4.5.0", "mocha": "^6.1.4", "nyc": "^14.0.0", - "parcel-bundler": "^1.12.3", + "parcel-bundler": "^1.12.4", "rimraf": "^2.6.3", "rollup": "^1.17.0", "rollup-plugin-cleanup": "^3.1.1", - "terser": "^4.1.2", + "terser": "^4.3.8", "ts-loader": "^6.0.4", "tslint": "^5.18.0", - "typescript": "^3.5.3", + "typescript": "^3.6.4", "webpack": "^4.35.3", "webpack-cli": "^3.3.6" }, diff --git a/packages/adjacency/CHANGELOG.md b/packages/adjacency/CHANGELOG.md index 49ff0f96d1..2797a105cd 100644 --- a/packages/adjacency/CHANGELOG.md +++ b/packages/adjacency/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/adjacency@0.1.24...@thi.ng/adjacency@0.1.25) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/adjacency + + + + + +## [0.1.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/adjacency@0.1.23...@thi.ng/adjacency@0.1.24) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/adjacency + + + + + +## [0.1.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/adjacency@0.1.22...@thi.ng/adjacency@0.1.23) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/adjacency + + + + + +## [0.1.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/adjacency@0.1.21...@thi.ng/adjacency@0.1.22) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/adjacency + + + + + +## [0.1.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/adjacency@0.1.20...@thi.ng/adjacency@0.1.21) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/adjacency + + + + + +## [0.1.20](https://github.com/thi-ng/umbrella/compare/@thi.ng/adjacency@0.1.19...@thi.ng/adjacency@0.1.20) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/adjacency + + + + + ## [0.1.19](https://github.com/thi-ng/umbrella/compare/@thi.ng/adjacency@0.1.18...@thi.ng/adjacency@0.1.19) (2019-07-31) **Note:** Version bump only for package @thi.ng/adjacency diff --git a/packages/adjacency/package.json b/packages/adjacency/package.json index eb71635b88..c403eecfcd 100644 --- a/packages/adjacency/package.json +++ b/packages/adjacency/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/adjacency", - "version": "0.1.19", + "version": "0.1.25", "description": "Sparse & bitwise adjacency matrices for directed / undirected graphs", "module": "./index.js", "main": "./lib/index.js", @@ -25,21 +25,21 @@ "pub": "yarn build:release && yarn publish --access public" }, "devDependencies": { - "@thi.ng/vectors": "^3.0.3", + "@thi.ng/vectors": "^4.0.0", "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/binary": "^1.1.0", - "@thi.ng/bitfield": "^0.1.11", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/dcons": "^2.1.2", - "@thi.ng/sparse": "^0.1.18" + "@thi.ng/api": "^6.5.0", + "@thi.ng/binary": "^1.1.1", + "@thi.ng/bitfield": "^0.2.1", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/dcons": "^2.1.6", + "@thi.ng/sparse": "^0.1.22" }, "keywords": [ "adjacency", diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index b33dc124e8..487478ad7c 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [6.5.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/api@6.4.0...@thi.ng/api@6.5.0) (2019-11-09) + + +### Features + +* **api:** add RangeXX types ([fc9cf21](https://github.com/thi-ng/umbrella/commit/fc9cf212ec97a719bca6f3214215f5d0aa479ea8)) +* **api:** add typedArray() factory, update type mappers, docs ([ac7fa13](https://github.com/thi-ng/umbrella/commit/ac7fa13fa602947ae4b30c943d8d8fddcd602381)) +* **api:** add types, split api.ts into separate files ([b72e664](https://github.com/thi-ng/umbrella/commit/b72e6641626314761488122a1bc55bc2e802eb74)) + + + + + +# [6.4.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/api@6.3.3...@thi.ng/api@6.4.0) (2019-09-21) + + +### Features + +* **api:** add Nullable ([8366223](https://github.com/thi-ng/umbrella/commit/8366223)) + + + + + +## [6.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/api@6.3.2...@thi.ng/api@6.3.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/api + + + + + ## [6.3.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/api@6.3.1...@thi.ng/api@6.3.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/api diff --git a/packages/api/package.json b/packages/api/package.json index 9c226a18bc..f947b19db6 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/api", - "version": "6.3.2", + "version": "6.5.0", "description": "Common, generic types & interfaces for thi.ng projects", "module": "./index.js", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "build:test": "rimraf build && tsc -p test/tsconfig.json", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", - "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib decorators mixins", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib api decorators mixins", "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", "pub": "yarn build:release && yarn publish --access public" }, @@ -29,11 +29,11 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/errors": "^1.1.2" + "@thi.ng/errors": "^1.2.1" }, "keywords": [ "compare", diff --git a/packages/api/src/api.ts b/packages/api/src/api.ts index bc9c57630a..5807b8823d 100644 --- a/packages/api/src/api.ts +++ b/packages/api/src/api.ts @@ -1,280 +1,18 @@ -export const DEFAULT_EPS = 1e-6; +import { TypedArray } from "./api/typedarray"; -export const EVENT_ALL = "*"; -export const EVENT_ENABLE = "enable"; -export const EVENT_DISABLE = "disable"; +export const DEFAULT_EPS = 1e-6; /** * Internal use only. **Do NOT use in user land code!** */ export const SEMAPHORE = Symbol(); -/** - * No-effect placeholder function. - */ -export const NO_OP = () => {}; - export type ArrayLikeIterable = ArrayLike & Iterable; -/** - * A no-arg function, returning T. - */ -export type Fn0 = () => T; - -/** - * A single arg function from A => B. - */ -export type Fn = (a: A) => B; - -/** - * A 2-arg function from A,B => C. - */ -export type Fn2 = (a: A, b: B) => C; - -/** - * A 3-arg function from A,B,C => D. - */ -export type Fn3 = (a: A, b: B, c: C) => D; - -/** - * A 4-arg function from A,B,C,D => E. - */ -export type Fn4 = (a: A, b: B, c: C, d: D) => E; - -/** - * A 5-arg function from A,B,C,D,E => F. - */ -export type Fn5 = (a: A, b: B, c: C, d: D, e: E) => F; - -/** - * A 6-arg function from A,B,C,D,E,F => G. - */ -export type Fn6 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F -) => G; - -/** - * A 7-arg function from A,B,C,D,E,F,G => H. - */ -export type Fn7 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G -) => H; - -/** - * A 8-arg function from A,B,C,D,E,F,G,H => I. - */ -export type Fn8 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H -) => I; - -/** - * A 9-arg function from A,B,C,D,E,F,G,H,I => J. - */ -export type Fn9 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i: I -) => J; - -/** - * A 10-arg function from A,B,C,D,E,F,G,H,I,J => K. - */ -export type Fn10 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i: I, - j: J -) => K; - -export type FnO = (a: A, ...xs: any[]) => B; - -export type FnO2 = (a: A, b: B, ...xs: any[]) => C; - -export type FnO3 = (a: A, b: B, c: C, ...xs: any[]) => D; - -export type FnO4 = (a: A, b: B, c: C, d: D, ...xs: any[]) => E; - -export type FnO5 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - ...xs: any[] -) => F; - -export type FnO6 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - ...xs: any[] -) => G; - -export type FnO7 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - ...xs: any[] -) => H; - -export type FnO8 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - ...xs: any[] -) => I; - -export type FnO9 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i: I, - ...xs: any[] -) => J; - -export type FnO10 = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i: I, - j: J, - ...xs: any[] -) => K; - -/** - * An untyped vararg arg function to type T. - */ -export type FnAny = (...xs: any[]) => T; - -/** - * A typed vararg arg function from A => B. - */ -export type FnAnyT = (...xs: A[]) => B; - -/* - * Utilities for extracting key types of nested objects. - */ -export type Keys = keyof T; -export type Keys1> = Keys; -export type Keys2, B extends Keys1> = Keys1; -export type Keys3< - T, - A extends Keys, - B extends Keys1, - C extends Keys2 -> = Keys2; -export type Keys4< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3 -> = Keys3; -export type Keys5< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4 -> = Keys4; - -/* - * Utilities for extracting value types from nested objects. - */ -export type Val1> = T[A]; -export type Val2, B extends Keys1> = Val1[B]; -export type Val3< - T, - A extends Keys, - B extends Keys1, - C extends Keys2 -> = Val2[C]; -export type Val4< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3 -> = Val3[D]; -export type Val5< - T, - A extends Keys, - B extends Keys1, - C extends Keys2, - D extends Keys3, - E extends Keys4 -> = Val4[E]; - -/** - * Generic 2-element comparator function type alias. Must follow this - * contract and return: - * - * - negative if `a < b` - * - zero if `a == b` - * - positive if `a > b` - */ -export type Comparator = Fn2; - -/** - * Event listener. - */ -export type Listener = Fn; - export type NumericArray = number[] | TypedArray; +export type Nullable = T | null | undefined; + export type Primitive = number | string | boolean | symbol; /** @@ -286,450 +24,3 @@ export type Path = PropertyKey | PropertyKey[]; * A key-value pair / tuple. */ export type Pair = [K, V]; - -/** - * Predicate function mapping given value to true/false. - */ -export type Predicate = Fn; - -/** - * Predicate function mapping given args to true/false. - */ -export type Predicate2 = Fn2; - -export type Select2 = T extends Q ? A : B; - -export type Select3 = T extends Q1 - ? A - : T extends Q2 - ? B - : C; - -export type Select4 = T extends Q1 - ? A - : T extends Q2 - ? B - : T extends Q3 - ? C - : D; - -/** - * Higher order `Predicate` builder. Possibly stateful. - */ -export type StatefulPredicate = Fn0>; - -/** - * Higher order `Predicate2` builder. Possibly stateful. - */ -export type StatefulPredicate2 = Fn0>; - -export type Tuple = [T, ...T[]] & { length: N }; - -export type IterableTuple = Tuple & Iterable; - -export type TypedArray = - | Float32Array - | Float64Array - | Int8Array - | Int16Array - | Int32Array - | Uint8Array - | Uint8ClampedArray - | Uint16Array - | Uint32Array; - -export type IntArray = Int8Array | Int16Array | Int32Array; -export type UIntArray = Uint8Array | Uint16Array | Uint32Array; -export type FloatArray = Float32Array | Float64Array; - -export const enum Type { - U8, - U8C, - I8, - U16, - I16, - U32, - I32, - F32, - F64 -} - -export const SIZEOF = { - [Type.U8]: 1, - [Type.U8C]: 1, - [Type.I8]: 1, - [Type.U16]: 2, - [Type.I16]: 2, - [Type.U32]: 4, - [Type.I32]: 4, - [Type.F32]: 4, - [Type.F64]: 8 -}; - -/** - * Observer function for `IWatch` implementations. - */ -export type Watch = (id: string, oldState: T, newState: T) => void; - -export enum LogLevel { - FINE, - DEBUG, - INFO, - WARN, - SEVERE, - NONE -} - -/** - * @param K key type - * @param V value type - * @param T return type - */ -export interface IAssoc { - assoc(key: K, val: V): T; - update(key: K, f: Fn): T; -} - -/** - * @param K key type - * @param V value type - * @param T return type - */ -export interface IAssocIn { - assocIn(key: K[], val: V): T; - updateIn(key: K[], f: Fn): T; -} - -/** - * Generic resource binding methods. - */ -export interface IBind { - /** - * @returns true, if successful - */ - bind(opt: T): boolean; - /** - * @returns true, if successful - */ - unbind(opt: T): boolean; -} - -/** - * Generic interface for types with binary backing buffers. - */ -export interface IBuffered { - /** - * An implementation's publicly accessible backing array / - * ArrayBuffer (usually a typed array instance). - */ - buffer: T; - /** - * Returns an Uint8Array view of backing array. - */ - bytes?(): Uint8Array; -} - -/** - * Generic interface to compare value types. - */ -export interface ICompare { - /** - * Compares this value with given value `x`. MUST follow same - * contract as `Comparator`. MUST return 0 if the type also - * implements `IEquiv` and `equiv` returns true for same `x`. - * - * Also see `IHash`. - * - * @param x - */ - compare(x: T): number; -} - -/** - * Generic interface for collection types to check if a given value is - * part of the collection. - */ -export interface IContains { - /** - * Returns `true` if `x` is part of collection. - * - * @param x - */ - contains(x: T): boolean; -} - -/** - * Generic interface for clonable types. - */ -export interface ICopy { - /** - * Returns a copy of this instance. Shallow or deep copies are - * implementation specific. - */ - copy(): T; -} - -/** - * Generic interface for reference types (value wrappers). - */ -export interface IDeref { - /** - * Returns wrapped value. - */ - deref(): T; -} - -/** - * Extension of `IAssoc` for types supporting key removals. - * - * @param K key type - * @param V value type - * @param T return type - */ -export interface IDissoc extends IAssoc { - dissoc(key: K): T; -} - -/** - * Extension of `IAssocIn` for types supporting key removals. - * - * @param K key type - * @param V value type - * @param T return type - */ -export interface IDissocIn extends IAssocIn { - dissocIn(key: K[]): T; -} - -export interface IEmpty { - /** - * Returns an empty/blank instance of same type (with possibly same - * config, if any). - */ - empty(): T; -} - -/** - * Interface to provide enabled/disabled functionality. Also see - * `@IEnable` decorator mixin - * - * @param T type for enable/disable option arg - */ -export interface IEnable { - isEnabled(): boolean; - /** - * Disables this entity. - * @param opts optional implementation specific arg - */ - disable(opts?: T): any; - /** - * Enables this entity. - * @param opts optional implementation specific arg - */ - enable(opts?: T): any; - toggle?(): boolean; -} - -export interface IEquiv { - /** - * Returns `true` if this *value* is equivalent to `o`. Also see - * `ICompare.compare` and `IHash.hash`. - * - * @param o - */ - equiv(o: any): boolean; -} - -/** - * @param T value type - */ -export interface IEqualsDelta { - /** - * Returns `true` if this value equals `o` with optional allowance - * for given tolerance `eps`. - * - * @param o 2nd value to test - * @param eps tolerance (usually defaults to `DEFAULT_EPS`) - */ - eqDelta(o: T, eps?: number): boolean; -} - -export interface Event extends IID { - target?: any; - canceled?: boolean; - value?: any; -} - -/** - * @param K key type - * @param V value type - */ -export interface IGet { - get(key: K, notfound?: V): V | undefined; -} - -/** - * @param K key type - * @param V value type - */ -export interface IGetIn { - getIn(key: K[], notfound?: V): V | undefined; -} - -/** - * Interface for hashable types. - */ -export interface IHash { - /** - * Returns a value's hash code. The contract of this function is: If - * `IEquiv.equiv` returns `true` for two values, their hash codes - * MUST also be equal. - */ - hash(): T; -} - -/** - * `id` property declaration. - */ -export interface IID { - readonly id: T; -} - -/** - * Interface for collection types which can be accessed via numeric - * index. - */ -export interface IIndexed { - nth(i: number, notfound: T): T; -} - -/** - * Interface for collection types supporting addition of multiple - * values. - */ -export interface IInto { - into(coll: Iterable): T; -} - -/** - * `length` property declaration for collections to obtain their element - * count. - */ -export interface ILength { - readonly length: number; -} - -export interface ILogger { - /** - * This logger's configured minimum log level - */ - level: LogLevel; - - fine(...args: any[]): void; - debug(...args: any[]): void; - info(...args: any[]): void; - warn(...args: any[]): void; - severe(...args: any[]): void; -} - -/** - * Generic interface for types supporting metadata. Implementations MUST - * exclude metadata from any comparisons, equality checks & hashing. - */ -export interface IMeta { - meta(): any; - /** - * Returns a copy of the original value with given metadata - * attached. - * - * @param meta - */ - withMeta(meta: any): T; -} - -/** - * Interface to provide event emitter functionality. Also see `@INotify` - * decorator mixin - */ -export interface INotify { - addListener(id: string, fn: Listener, scope?: any): boolean; - removeListener(id: string, fn: Listener, scope?: any): boolean; - notify(event: Event): void; -} - -/** - * Generic plain object with all key values of given type. - */ -export interface IObjectOf { - [id: string]: T; -} - -/** - * Interface for types supported the release of internal resources. - */ -export interface IRelease { - release(opt?: any): boolean; -} - -/** - * Generic interface for set collection types. - * - * @param V value type - * @param T return type - */ -export interface ISet extends IInto { - /** - * Conjoins/adds value `x` to set. - * - * @param x - */ - conj(x: V): T; - /** - * Disjoins/removes value `x` from set. - * - * @param x - */ - disj(x: V): T; -} - -/** - * Generic interface for collections implementing - * stack functionality. - * - * @param V value type - * @param P return type for pop() - * @param S return type for push() - */ -export interface IStack { - /** - * Returns top-of-stack item. - */ - peek(): V | undefined; - /** - * Removes top-of-stack item and returns type P. - */ - pop(): P | undefined; - push(x: V): S; -} - -export interface IToHiccup { - /** - * Returns a thi.ng/hiccup compatible representation. The optional - * `ctx` arg is an arbitrary user context object passed to all - * hiccup components during serialization (or during DOM creation / - * update if used with thi.ng/hdom) - * - * @param ctx user context object - */ - toHiccup(ctx?: any): any; -} - -/** - * Interface for types offering observers of internal value changes. - * Also see `@IWatch` decorator mixin. - */ -export interface IWatch { - addWatch(id: string, fn: Watch): boolean; - removeWatch(id: string): boolean; - notifyWatches(oldState: T, newState: T): void; -} diff --git a/packages/api/src/api/assoc.ts b/packages/api/src/api/assoc.ts new file mode 100644 index 0000000000..8ebf5ed202 --- /dev/null +++ b/packages/api/src/api/assoc.ts @@ -0,0 +1,21 @@ +import { Fn } from "./fn"; + +/** + * @param K key type + * @param V value type + * @param T return type + */ +export interface IAssoc { + assoc(key: K, val: V): T; + update(key: K, f: Fn): T; +} + +/** + * @param K key type + * @param V value type + * @param T return type + */ +export interface IAssocIn { + assocIn(key: K[], val: V): T; + updateIn(key: K[], f: Fn): T; +} diff --git a/packages/api/src/api/bind.ts b/packages/api/src/api/bind.ts new file mode 100644 index 0000000000..1b9369b6c1 --- /dev/null +++ b/packages/api/src/api/bind.ts @@ -0,0 +1,13 @@ +/** + * Generic resource binding methods. + */ +export interface IBind { + /** + * @returns true, if successful + */ + bind(opt: T): boolean; + /** + * @returns true, if successful + */ + unbind(opt: T): boolean; +} diff --git a/packages/api/src/api/buffered.ts b/packages/api/src/api/buffered.ts new file mode 100644 index 0000000000..5f8b14ec19 --- /dev/null +++ b/packages/api/src/api/buffered.ts @@ -0,0 +1,14 @@ +/** + * Generic interface for types with binary backing buffers. + */ +export interface IBuffered { + /** + * An implementation's publicly accessible backing array / + * ArrayBuffer (usually a typed array instance). + */ + buffer: T; + /** + * Returns an Uint8Array view of backing array. + */ + bytes?(): Uint8Array; +} diff --git a/packages/api/src/api/compare.ts b/packages/api/src/api/compare.ts new file mode 100644 index 0000000000..96c4470970 --- /dev/null +++ b/packages/api/src/api/compare.ts @@ -0,0 +1,27 @@ +import { Fn2 } from "./fn"; + +/** + * Generic 2-element comparator function type alias. Must follow this + * contract and return: + * + * - negative if `a < b` + * - zero if `a == b` + * - positive if `a > b` + */ +export type Comparator = Fn2; + +/** + * Generic interface to compare value types. + */ +export interface ICompare { + /** + * Compares this value with given value `x`. MUST follow same + * contract as `Comparator`. MUST return 0 if the type also + * implements `IEquiv` and `equiv` returns true for same `x`. + * + * Also see `IHash`. + * + * @param x + */ + compare(x: T): number; +} diff --git a/packages/api/src/api/contains.ts b/packages/api/src/api/contains.ts new file mode 100644 index 0000000000..6e26deb1a1 --- /dev/null +++ b/packages/api/src/api/contains.ts @@ -0,0 +1,12 @@ +/** + * Generic interface for collection types to check if a given value is + * part of the collection. + */ +export interface IContains { + /** + * Returns `true` if `x` is part of collection. + * + * @param x + */ + contains(x: T): boolean; +} diff --git a/packages/api/src/api/copy.ts b/packages/api/src/api/copy.ts new file mode 100644 index 0000000000..9a70c4f8a9 --- /dev/null +++ b/packages/api/src/api/copy.ts @@ -0,0 +1,10 @@ +/** + * Generic interface for clonable types. + */ +export interface ICopy { + /** + * Returns a copy of this instance. Shallow or deep copies are + * implementation specific. + */ + copy(): T; +} diff --git a/packages/api/src/api/deref.ts b/packages/api/src/api/deref.ts new file mode 100644 index 0000000000..0cc940bd80 --- /dev/null +++ b/packages/api/src/api/deref.ts @@ -0,0 +1,9 @@ +/** + * Generic interface for reference types (value wrappers). + */ +export interface IDeref { + /** + * Returns wrapped value. + */ + deref(): T; +} diff --git a/packages/api/src/api/dissoc.ts b/packages/api/src/api/dissoc.ts new file mode 100644 index 0000000000..18b9b38fa1 --- /dev/null +++ b/packages/api/src/api/dissoc.ts @@ -0,0 +1,23 @@ +import { IAssoc, IAssocIn } from "./assoc"; + +/** + * Extension of `IAssoc` for types supporting key removals. + * + * @param K key type + * @param V value type + * @param T return type + */ +export interface IDissoc extends IAssoc { + dissoc(key: K): T; +} + +/** + * Extension of `IAssocIn` for types supporting key removals. + * + * @param K key type + * @param V value type + * @param T return type + */ +export interface IDissocIn extends IAssocIn { + dissocIn(key: K[]): T; +} diff --git a/packages/api/src/api/empty.ts b/packages/api/src/api/empty.ts new file mode 100644 index 0000000000..ed5db397f1 --- /dev/null +++ b/packages/api/src/api/empty.ts @@ -0,0 +1,7 @@ +export interface IEmpty { + /** + * Returns an empty/blank instance of same type (with possibly same + * config, if any). + */ + empty(): T; +} diff --git a/packages/api/src/api/enable.ts b/packages/api/src/api/enable.ts new file mode 100644 index 0000000000..bbf3992f16 --- /dev/null +++ b/packages/api/src/api/enable.ts @@ -0,0 +1,20 @@ +/** + * Interface to provide enabled/disabled functionality. Also see + * `@IEnable` decorator mixin + * + * @param T type for enable/disable option arg + */ +export interface IEnable { + isEnabled(): boolean; + /** + * Disables this entity. + * @param opts optional implementation specific arg + */ + disable(opts?: T): any; + /** + * Enables this entity. + * @param opts optional implementation specific arg + */ + enable(opts?: T): any; + toggle?(): boolean; +} diff --git a/packages/api/src/api/equiv.ts b/packages/api/src/api/equiv.ts new file mode 100644 index 0000000000..6b3cafc780 --- /dev/null +++ b/packages/api/src/api/equiv.ts @@ -0,0 +1,23 @@ +export interface IEquiv { + /** + * Returns `true` if this *value* is equivalent to `o`. Also see + * `ICompare.compare` and `IHash.hash`. + * + * @param o + */ + equiv(o: any): boolean; +} + +/** + * @param T value type + */ +export interface IEqualsDelta { + /** + * Returns `true` if this value equals `o` with optional allowance + * for given tolerance `eps`. + * + * @param o 2nd value to test + * @param eps tolerance (usually defaults to `DEFAULT_EPS`) + */ + eqDelta(o: T, eps?: number): boolean; +} diff --git a/packages/api/src/api/event.ts b/packages/api/src/api/event.ts new file mode 100644 index 0000000000..cd9174a31e --- /dev/null +++ b/packages/api/src/api/event.ts @@ -0,0 +1,27 @@ +import { Fn } from "./fn"; +import { IID } from "./id"; + +export const EVENT_ALL = "*"; +export const EVENT_ENABLE = "enable"; +export const EVENT_DISABLE = "disable"; + +/** + * Event listener. + */ +export type Listener = Fn; + +export interface Event extends IID { + target?: any; + canceled?: boolean; + value?: any; +} + +/** + * Interface to provide event emitter functionality. Also see `@INotify` + * decorator mixin + */ +export interface INotify { + addListener(id: string, fn: Listener, scope?: any): boolean; + removeListener(id: string, fn: Listener, scope?: any): boolean; + notify(event: Event): void; +} diff --git a/packages/api/src/api/fn.ts b/packages/api/src/api/fn.ts new file mode 100644 index 0000000000..3fe0770a94 --- /dev/null +++ b/packages/api/src/api/fn.ts @@ -0,0 +1,191 @@ +/** + * No-effect placeholder function. + */ +export const NO_OP = () => {}; + +/** + * A no-arg function, returning T. + */ +export type Fn0 = () => T; + +/** + * A single arg function from A => B. + */ +export type Fn = (a: A) => B; + +/** + * A 2-arg function from A,B => C. + */ +export type Fn2 = (a: A, b: B) => C; + +/** + * A 3-arg function from A,B,C => D. + */ +export type Fn3 = (a: A, b: B, c: C) => D; + +/** + * A 4-arg function from A,B,C,D => E. + */ +export type Fn4 = (a: A, b: B, c: C, d: D) => E; + +/** + * A 5-arg function from A,B,C,D,E => F. + */ +export type Fn5 = (a: A, b: B, c: C, d: D, e: E) => F; + +/** + * A 6-arg function from A,B,C,D,E,F => G. + */ +export type Fn6 = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F +) => G; + +/** + * A 7-arg function from A,B,C,D,E,F,G => H. + */ +export type Fn7 = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G +) => H; + +/** + * A 8-arg function from A,B,C,D,E,F,G,H => I. + */ +export type Fn8 = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H +) => I; + +/** + * A 9-arg function from A,B,C,D,E,F,G,H,I => J. + */ +export type Fn9 = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I +) => J; + +/** + * A 10-arg function from A,B,C,D,E,F,G,H,I,J => K. + */ +export type Fn10 = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J +) => K; + +export type FnO = (a: A, ...xs: any[]) => B; + +export type FnO2 = (a: A, b: B, ...xs: any[]) => C; + +export type FnO3 = (a: A, b: B, c: C, ...xs: any[]) => D; + +export type FnO4 = (a: A, b: B, c: C, d: D, ...xs: any[]) => E; + +export type FnO5 = ( + a: A, + b: B, + c: C, + d: D, + e: E, + ...xs: any[] +) => F; + +export type FnO6 = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + ...xs: any[] +) => G; + +export type FnO7 = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + ...xs: any[] +) => H; + +export type FnO8 = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + ...xs: any[] +) => I; + +export type FnO9 = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + ...xs: any[] +) => J; + +export type FnO10 = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i: I, + j: J, + ...xs: any[] +) => K; + +/** + * An untyped vararg arg function to type T. + */ +export type FnAny = (...xs: any[]) => T; + +/** + * A typed vararg arg function from A => B. + */ +export type FnAnyT = (...xs: A[]) => B; diff --git a/packages/api/src/api/get.ts b/packages/api/src/api/get.ts new file mode 100644 index 0000000000..8ea2de3317 --- /dev/null +++ b/packages/api/src/api/get.ts @@ -0,0 +1,15 @@ +/** + * @param K key type + * @param V value type + */ +export interface IGet { + get(key: K, notfound?: V): V | undefined; +} + +/** + * @param K key type + * @param V value type + */ +export interface IGetIn { + getIn(key: K[], notfound?: V): V | undefined; +} diff --git a/packages/api/src/api/hash.ts b/packages/api/src/api/hash.ts new file mode 100644 index 0000000000..7047d120f4 --- /dev/null +++ b/packages/api/src/api/hash.ts @@ -0,0 +1,11 @@ +/** + * Interface for hashable types. + */ +export interface IHash { + /** + * Returns a value's hash code. The contract of this function is: If + * `IEquiv.equiv` returns `true` for two values, their hash codes + * MUST also be equal. + */ + hash(): T; +} diff --git a/packages/api/src/api/hiccup.ts b/packages/api/src/api/hiccup.ts new file mode 100644 index 0000000000..fa42bf9c17 --- /dev/null +++ b/packages/api/src/api/hiccup.ts @@ -0,0 +1,11 @@ +export interface IToHiccup { + /** + * Returns a thi.ng/hiccup compatible representation. The optional + * `ctx` arg is an arbitrary user context object passed to all + * hiccup components during serialization (or during DOM creation / + * update if used with thi.ng/hdom) + * + * @param ctx user context object + */ + toHiccup(ctx?: any): any; +} diff --git a/packages/api/src/api/id.ts b/packages/api/src/api/id.ts new file mode 100644 index 0000000000..a04ab98410 --- /dev/null +++ b/packages/api/src/api/id.ts @@ -0,0 +1,6 @@ +/** + * `id` property declaration. + */ +export interface IID { + readonly id: T; +} diff --git a/packages/api/src/api/indexed.ts b/packages/api/src/api/indexed.ts new file mode 100644 index 0000000000..502a7be167 --- /dev/null +++ b/packages/api/src/api/indexed.ts @@ -0,0 +1,7 @@ +/** + * Interface for collection types which can be accessed via numeric + * index. + */ +export interface IIndexed { + nth(i: number, notfound: T): T; +} diff --git a/packages/api/src/api/into.ts b/packages/api/src/api/into.ts new file mode 100644 index 0000000000..cc2399d534 --- /dev/null +++ b/packages/api/src/api/into.ts @@ -0,0 +1,7 @@ +/** + * Interface for collection types supporting addition of multiple + * values. + */ +export interface IInto { + into(coll: Iterable): T; +} diff --git a/packages/api/src/api/keyval.ts b/packages/api/src/api/keyval.ts new file mode 100644 index 0000000000..fac8a02336 --- /dev/null +++ b/packages/api/src/api/keyval.ts @@ -0,0 +1,54 @@ +/* + * Utilities for extracting key types of nested objects. + */ +export type Keys = keyof T; +export type Keys1> = Keys; +export type Keys2, B extends Keys1> = Keys1; +export type Keys3< + T, + A extends Keys, + B extends Keys1, + C extends Keys2 +> = Keys2; +export type Keys4< + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3 +> = Keys3; +export type Keys5< + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4 +> = Keys4; + +/* + * Utilities for extracting value types from nested objects. + */ +export type Val1> = T[A]; +export type Val2, B extends Keys1> = Val1[B]; +export type Val3< + T, + A extends Keys, + B extends Keys1, + C extends Keys2 +> = Val2[C]; +export type Val4< + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3 +> = Val3[D]; +export type Val5< + T, + A extends Keys, + B extends Keys1, + C extends Keys2, + D extends Keys3, + E extends Keys4 +> = Val4[E]; diff --git a/packages/api/src/api/length.ts b/packages/api/src/api/length.ts new file mode 100644 index 0000000000..5529f858d3 --- /dev/null +++ b/packages/api/src/api/length.ts @@ -0,0 +1,7 @@ +/** + * `length` property declaration for collections to obtain their element + * count. + */ +export interface ILength { + readonly length: number; +} diff --git a/packages/api/src/api/logger.ts b/packages/api/src/api/logger.ts new file mode 100644 index 0000000000..be19ab0d9b --- /dev/null +++ b/packages/api/src/api/logger.ts @@ -0,0 +1,21 @@ +export enum LogLevel { + FINE, + DEBUG, + INFO, + WARN, + SEVERE, + NONE +} + +export interface ILogger { + /** + * This logger's configured minimum log level + */ + level: LogLevel; + + fine(...args: any[]): void; + debug(...args: any[]): void; + info(...args: any[]): void; + warn(...args: any[]): void; + severe(...args: any[]): void; +} diff --git a/packages/api/src/api/meta.ts b/packages/api/src/api/meta.ts new file mode 100644 index 0000000000..f5fca87ea1 --- /dev/null +++ b/packages/api/src/api/meta.ts @@ -0,0 +1,14 @@ +/** + * Generic interface for types supporting metadata. Implementations MUST + * exclude metadata from any comparisons, equality checks & hashing. + */ +export interface IMeta { + meta(): any; + /** + * Returns a copy of the original value with given metadata + * attached. + * + * @param meta + */ + withMeta(meta: any): T; +} diff --git a/packages/api/src/api/object.ts b/packages/api/src/api/object.ts new file mode 100644 index 0000000000..46ab13d481 --- /dev/null +++ b/packages/api/src/api/object.ts @@ -0,0 +1,6 @@ +/** + * Generic plain object with all key values of given type. + */ +export interface IObjectOf { + [id: string]: T; +} diff --git a/packages/api/src/api/predicate.ts b/packages/api/src/api/predicate.ts new file mode 100644 index 0000000000..e65f1d3dfd --- /dev/null +++ b/packages/api/src/api/predicate.ts @@ -0,0 +1,21 @@ +import { Fn, Fn0, Fn2 } from "./fn"; + +/** + * Predicate function mapping given value to true/false. + */ +export type Predicate = Fn; + +/** + * Predicate function mapping given args to true/false. + */ +export type Predicate2 = Fn2; + +/** + * Higher order `Predicate` builder. Possibly stateful. + */ +export type StatefulPredicate = Fn0>; + +/** + * Higher order `Predicate2` builder. Possibly stateful. + */ +export type StatefulPredicate2 = Fn0>; diff --git a/packages/api/src/api/range.ts b/packages/api/src/api/range.ts new file mode 100644 index 0000000000..349548a35c --- /dev/null +++ b/packages/api/src/api/range.ts @@ -0,0 +1,36 @@ +export type Range0_1 = 0 | 1; + +export type Range0_3 = Range0_1 | 2 | 3; + +export type Range0_7 = Range0_3 | Range4_7; + +export type Range0_15 = Range0_7 | Range8_15; + +export type Range0_31 = Range0_15 | Range16_31; + +export type Range0_63 = Range0_31 | Range32_63; + +export type Range1_2 = 1 | 2; + +export type Range1_4 = Range1_2 | 3 | 4; + +export type Range1_8 = Range1_4 | Range4_7 | 8; + +export type Range1_16 = Range1_8 | Range8_15 | 16; + +export type Range1_32 = Range1_16 | Range16_31 | 32; + +export type Range1_64 = Range1_32 | Range32_63 | 64; + +export type Range4_7 = 4 | 5 | 6 | 7; + +export type Range8_15 = 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15; + +// prettier-ignore +export type Range16_31 = + | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31; + +// prettier-ignore +export type Range32_63 = + | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 + | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63; diff --git a/packages/api/src/api/release.ts b/packages/api/src/api/release.ts new file mode 100644 index 0000000000..d763ea3277 --- /dev/null +++ b/packages/api/src/api/release.ts @@ -0,0 +1,6 @@ +/** + * Interface for types supported the release of internal resources. + */ +export interface IRelease { + release(opt?: any): boolean; +} diff --git a/packages/api/src/api/select.ts b/packages/api/src/api/select.ts new file mode 100644 index 0000000000..f2b2e8ea6e --- /dev/null +++ b/packages/api/src/api/select.ts @@ -0,0 +1,15 @@ +export type Select2 = T extends Q ? A : B; + +export type Select3 = T extends Q1 + ? A + : T extends Q2 + ? B + : C; + +export type Select4 = T extends Q1 + ? A + : T extends Q2 + ? B + : T extends Q3 + ? C + : D; diff --git a/packages/api/src/api/set.ts b/packages/api/src/api/set.ts new file mode 100644 index 0000000000..9d8ffbecc1 --- /dev/null +++ b/packages/api/src/api/set.ts @@ -0,0 +1,22 @@ +import { IInto } from "./into"; + +/** + * Generic interface for set collection types. + * + * @param V value type + * @param T return type + */ +export interface ISet extends IInto { + /** + * Conjoins/adds value `x` to set. + * + * @param x + */ + conj(x: V): T; + /** + * Disjoins/removes value `x` from set. + * + * @param x + */ + disj(x: V): T; +} diff --git a/packages/api/src/api/stack.ts b/packages/api/src/api/stack.ts new file mode 100644 index 0000000000..1844d1f915 --- /dev/null +++ b/packages/api/src/api/stack.ts @@ -0,0 +1,19 @@ +/** + * Generic interface for collections implementing + * stack functionality. + * + * @param V value type + * @param P return type for pop() + * @param S return type for push() + */ +export interface IStack { + /** + * Returns top-of-stack item. + */ + peek(): V | undefined; + /** + * Removes top-of-stack item and returns type P. + */ + pop(): P | undefined; + push(x: V): S; +} diff --git a/packages/api/src/api/tuple.ts b/packages/api/src/api/tuple.ts new file mode 100644 index 0000000000..5644630578 --- /dev/null +++ b/packages/api/src/api/tuple.ts @@ -0,0 +1,3 @@ +export type Tuple = [T, ...T[]] & { length: N }; + +export type IterableTuple = Tuple & Iterable; diff --git a/packages/api/src/api/typedarray.ts b/packages/api/src/api/typedarray.ts new file mode 100644 index 0000000000..6350e1a715 --- /dev/null +++ b/packages/api/src/api/typedarray.ts @@ -0,0 +1,165 @@ +export type TypedArray = + | Float32Array + | Float64Array + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array; + +export type IntArray = Int8Array | Int16Array | Int32Array; +export type UIntArray = Uint8Array | Uint16Array | Uint32Array; +export type FloatArray = Float32Array | Float64Array; + +export type TypedArrayConstructor = + | Uint8ArrayConstructor + | Uint8ClampedArrayConstructor + | Int8ArrayConstructor + | Uint16ArrayConstructor + | Int16ArrayConstructor + | Uint32ArrayConstructor + | Int32ArrayConstructor + | Float32ArrayConstructor + | Float64ArrayConstructor; + +/** + * Type enums for Typedarray-backed buffers. + * + * @see GLType + * @see GL2TYPE + * @see TYPE2GL + */ +export const enum Type { + U8, + U8C, + I8, + U16, + I16, + U32, + I32, + F32, + F64 +} + +/** + * WebGL numeric type constants. Use `GL2TYPE` to convert, if needed. + * + * @see Type + * @see GL2TYPE + * @see TYPE2GL + */ +export const enum GLType { + I8 = 0x1400, + U8 = 0x1401, + I16 = 0x1402, + U16 = 0x1403, + I32 = 0x1404, + U32 = 0x1405, + F32 = 0x1406 +} + +/** + * Conversion from `GLType` to `Type` enums. + */ +export const GL2TYPE: Record = { + [GLType.I8]: Type.I8, + [GLType.U8]: Type.U8, + [GLType.I16]: Type.I16, + [GLType.U16]: Type.U16, + [GLType.I32]: Type.I32, + [GLType.U32]: Type.U32, + [GLType.F32]: Type.F32 +}; + +/** + * Potentially lossy conversion from `Type` to `GLType` enums. + * + * Not all enums are mappable: + * + * - `F64` maps to `undefined`, since unsupported by WebGL + * - `U8C` maps to U8 + */ +export const TYPE2GL: Record = { + [Type.I8]: GLType.I8, + [Type.U8]: GLType.U8, + [Type.U8C]: GLType.U8, + [Type.I16]: GLType.I16, + [Type.U16]: GLType.U16, + [Type.I32]: GLType.I32, + [Type.I32]: GLType.I32, + [Type.U32]: GLType.U32, + [Type.F32]: GLType.F32, + [Type.F64]: undefined +}; + +/** + * Size information (in bytes) for `Type` enums. For `GLType`, use this + * form, e.g. `SIZEOF[GL2TYPE[GLType.F32]]` + */ +export const SIZEOF = { + [Type.U8]: 1, + [Type.U8C]: 1, + [Type.I8]: 1, + [Type.U16]: 2, + [Type.I16]: 2, + [Type.U32]: 4, + [Type.I32]: 4, + [Type.F32]: 4, + [Type.F64]: 8 +}; + +export const TYPEDARRAY_CTORS: Record = { + [Type.U8]: Uint8Array, + [Type.U8C]: Uint8ClampedArray, + [Type.I8]: Int8Array, + [Type.U16]: Uint16Array, + [Type.I16]: Int16Array, + [Type.U32]: Uint32Array, + [Type.I32]: Int32Array, + [Type.F32]: Float32Array, + [Type.F64]: Float64Array, + [GLType.U8]: Uint8Array, + [GLType.I8]: Int8Array, + [GLType.U16]: Uint16Array, + [GLType.I16]: Int16Array, + [GLType.U32]: Uint32Array, + [GLType.I32]: Int32Array, + [GLType.F32]: Float32Array +}; + +export interface TypedArrayTypeMap extends Record { + [Type.U8]: Uint8Array; + [Type.U8C]: Uint8ClampedArray; + [Type.I8]: Int8Array; + [Type.U16]: Uint16Array; + [Type.I16]: Int16Array; + [Type.U32]: Uint32Array; + [Type.I32]: Int32Array; + [Type.F32]: Float32Array; + [Type.F64]: Float64Array; + [GLType.U8]: Uint8Array; + [GLType.I8]: Int8Array; + [GLType.U16]: Uint16Array; + [GLType.I16]: Int16Array; + [GLType.U32]: Uint32Array; + [GLType.I32]: Int32Array; + [GLType.F32]: Float32Array; +} + +/** + * Constructs new typed array of given `Type`/`GLType`. Supports all + * arities of standard typed array ctors. + * + * @param type + */ +// prettier-ignore +export function typedArray(type: T, length: number): TypedArrayTypeMap[T]; +// prettier-ignore +export function typedArray(type: T, src: ArrayLike | ArrayBufferLike): TypedArrayTypeMap[T]; +// prettier-ignore +export function typedArray(type: T, buf: ArrayBufferLike, byteOffset: number, length?: number): TypedArrayTypeMap[T]; +export function typedArray(type: T, ...xs: any[]) { + return new (TYPEDARRAY_CTORS[type])(...xs); +} diff --git a/packages/api/src/api/watch.ts b/packages/api/src/api/watch.ts new file mode 100644 index 0000000000..2696ab662c --- /dev/null +++ b/packages/api/src/api/watch.ts @@ -0,0 +1,14 @@ +/** + * Observer function for `IWatch` implementations. + */ +export type Watch = (id: string, oldState: T, newState: T) => void; + +/** + * Interface for types offering observers of internal value changes. + * Also see `@IWatch` decorator mixin. + */ +export interface IWatch { + addWatch(id: string, fn: Watch): boolean; + removeWatch(id: string): boolean; + notifyWatches(oldState: T, newState: T): void; +} diff --git a/packages/api/src/assert.ts b/packages/api/src/assert.ts index 8fd2c688f3..0a3184f3f0 100644 --- a/packages/api/src/assert.ts +++ b/packages/api/src/assert.ts @@ -1,4 +1,4 @@ -import { Fn0, NO_OP } from "./api"; +import { Fn0, NO_OP } from "./api/fn"; /** * Takes a `test` result or predicate function without args and throws diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index f8deb6fc43..9cc033784e 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,4 +1,37 @@ export * from "./api"; +export * from "./api/assoc"; +export * from "./api/bind"; +export * from "./api/buffered"; +export * from "./api/compare"; +export * from "./api/contains"; +export * from "./api/copy"; +export * from "./api/deref"; +export * from "./api/dissoc"; +export * from "./api/empty"; +export * from "./api/enable"; +export * from "./api/equiv"; +export * from "./api/event"; +export * from "./api/fn"; +export * from "./api/get"; +export * from "./api/hash"; +export * from "./api/hiccup"; +export * from "./api/id"; +export * from "./api/indexed"; +export * from "./api/into"; +export * from "./api/keyval"; +export * from "./api/length"; +export * from "./api/logger"; +export * from "./api/meta"; +export * from "./api/object"; +export * from "./api/predicate"; +export * from "./api/range"; +export * from "./api/release"; +export * from "./api/select"; +export * from "./api/set"; +export * from "./api/stack"; +export * from "./api/tuple"; +export * from "./api/typedarray"; +export * from "./api/watch"; export * from "./assert"; export * from "./logger"; diff --git a/packages/api/src/logger.ts b/packages/api/src/logger.ts index f75857bfae..1b1be05570 100644 --- a/packages/api/src/logger.ts +++ b/packages/api/src/logger.ts @@ -1,4 +1,4 @@ -import { ILogger, LogLevel } from "./api"; +import { ILogger, LogLevel } from "./api/logger"; export const NULL_LOGGER: ILogger = Object.freeze({ level: LogLevel.NONE, diff --git a/packages/api/src/mixins/ienable.ts b/packages/api/src/mixins/ienable.ts index d9a86311c3..38657952ea 100644 --- a/packages/api/src/mixins/ienable.ts +++ b/packages/api/src/mixins/ienable.ts @@ -1,9 +1,5 @@ -import { - Event, - EVENT_DISABLE, - EVENT_ENABLE, - IEnable -} from "../api"; +import { IEnable } from "../api/enable"; +import { Event, EVENT_DISABLE, EVENT_ENABLE } from "../api/event"; import { mixin } from "../mixin"; interface _IEnable extends IEnable { @@ -20,28 +16,26 @@ interface _IEnable extends IEnable { export const IEnableMixin = mixin(>{ _enabled: true, - isEnabled() { - return (<_IEnable>this)._enabled; + isEnabled(this: _IEnable) { + return this._enabled; }, - enable() { - const $this = <_IEnable>this; - $this._enabled = true; - if ($this.notify) { - $this.notify({ id: EVENT_ENABLE, target: this }); + enable(this: _IEnable) { + this._enabled = true; + if (this.notify) { + this.notify({ id: EVENT_ENABLE, target: this }); } }, - disable() { - const $this = <_IEnable>this; - $this._enabled = false; - if ($this.notify) { - $this.notify({ id: EVENT_DISABLE, target: this }); + disable(this: _IEnable) { + this._enabled = false; + if (this.notify) { + this.notify({ id: EVENT_DISABLE, target: this }); } }, - toggle() { - (<_IEnable>this)._enabled ? this.disable() : this.enable(); - return (<_IEnable>this)._enabled; + toggle(this: _IEnable) { + this._enabled ? this.disable() : this.enable(); + return this._enabled; } }); diff --git a/packages/api/src/mixins/inotify.ts b/packages/api/src/mixins/inotify.ts index 6d9b4f33c1..a98ae84550 100644 --- a/packages/api/src/mixins/inotify.ts +++ b/packages/api/src/mixins/inotify.ts @@ -2,9 +2,9 @@ import { Event, EVENT_ALL, INotify, - IObjectOf, Listener -} from "../api"; +} from "../api/event"; +import { IObjectOf } from "../api/object"; import { mixin } from "../mixin"; interface _INotify extends INotify { @@ -29,37 +29,37 @@ export const inotify_dispatch = (listeners: any[][], e: Event) => { * registered listeners. */ export const INotifyMixin = mixin({ - addListener(id: string, fn: Listener, scope?: any) { - let l = ((<_INotify>this)._listeners = - (<_INotify>this)._listeners || {})[id]; - if (!l) { - l = (this)._listeners[id] = []; - } - if ((<_INotify>this).__listener(l, fn, scope) === -1) { + addListener(this: _INotify, id: string, fn: Listener, scope?: any) { + let l = (this._listeners = this._listeners || {})[id]; + !l && (l = this._listeners[id] = []); + if (this.__listener(l, fn, scope) === -1) { l.push([fn, scope]); return true; } return false; }, - removeListener(id: string, fn: Listener, scope?: any) { - if (!(<_INotify>this)._listeners) return false; - const l = (<_INotify>this)._listeners[id]; + removeListener(this: _INotify, id: string, fn: Listener, scope?: any) { + let listeners: IObjectOf<[Listener, any][]>; + if (!(listeners = this._listeners)) return false; + const l = listeners[id]; if (l) { - const idx = (<_INotify>this).__listener(l, fn, scope); + const idx = this.__listener(l, fn, scope); if (idx !== -1) { l.splice(idx, 1); + !l.length && delete listeners[id]; return true; } } return false; }, - notify(e: Event) { - if (!(<_INotify>this)._listeners) return; + notify(this: _INotify, e: Event) { + let listeners: IObjectOf<[Listener, any][]>; + if (!(listeners = this._listeners)) return false; e.target === undefined && (e.target = this); - inotify_dispatch((<_INotify>this)._listeners[e.id], e); - inotify_dispatch((<_INotify>this)._listeners[EVENT_ALL], e); + inotify_dispatch(listeners[e.id], e); + inotify_dispatch(listeners[EVENT_ALL], e); }, __listener(listeners: [Listener, any][], f: Listener, scope: any) { diff --git a/packages/api/src/mixins/iwatch.ts b/packages/api/src/mixins/iwatch.ts index fcbe9400ac..49ea635278 100644 --- a/packages/api/src/mixins/iwatch.ts +++ b/packages/api/src/mixins/iwatch.ts @@ -1,9 +1,5 @@ -import { - Fn3, - IObjectOf, - IWatch, - Watch -} from "../api"; +import { IObjectOf } from "../api/object"; +import { IWatch, Watch } from "../api/watch"; import { mixin } from "../mixin"; interface _IWatch extends IWatch { @@ -11,27 +7,27 @@ interface _IWatch extends IWatch { } export const IWatchMixin = mixin(>{ - addWatch(id: string, fn: Fn3) { - (<_IWatch>this)._watches = (<_IWatch>this)._watches || {}; - if ((<_IWatch>this)._watches[id]) { + addWatch(this: _IWatch, id: string, fn: Watch) { + this._watches = this._watches || {}; + if (this._watches[id]) { return false; } - (<_IWatch>this)._watches[id] = fn; + this._watches[id] = fn; return true; }, - removeWatch(id: string) { - if (!(<_IWatch>this)._watches) return; - if ((<_IWatch>this)._watches[id]) { - delete (<_IWatch>this)._watches[id]; + removeWatch(this: _IWatch, id: string) { + if (!this._watches) return; + if (this._watches[id]) { + delete this._watches[id]; return true; } return false; }, - notifyWatches(oldState: any, newState: any) { - if (!(<_IWatch>this)._watches) return; - const w = (<_IWatch>this)._watches; + notifyWatches(this: _IWatch, oldState: any, newState: any) { + if (!this._watches) return; + const w = this._watches; for (let id in w) { w[id](id, oldState, newState); } diff --git a/packages/api/test/mixins.ts b/packages/api/test/mixins.ts index 1135ec5565..da5592a2e3 100644 --- a/packages/api/test/mixins.ts +++ b/packages/api/test/mixins.ts @@ -3,9 +3,9 @@ import { Event, EVENT_ALL, INotify, + INotifyMixin, Listener -} from "../src/api"; -import { INotifyMixin } from "../src/mixins/inotify"; +} from "../src"; describe("mixins", () => { it("INotify", () => { @@ -26,14 +26,18 @@ describe("mixins", () => { const foo = new Foo(); const l = (e: Event) => (res[e.id] = e.value); const lall = (e: Event) => (res[EVENT_ALL] = e.value); - assert.doesNotThrow(() => foo.addListener("x", l)); - assert.doesNotThrow(() => foo.addListener(EVENT_ALL, lall)); + assert.ok(foo.addListener("x", l)); + assert.ok(foo.addListener(EVENT_ALL, lall)); foo.notify({ id: "x", value: 1 }); assert.deepEqual(res, { x: 1, [EVENT_ALL]: 1 }); - assert.doesNotThrow(() => foo.removeListener("x", l)); + assert.ok(foo.removeListener("x", l)); + assert.ok((foo)._listeners.x === undefined); + assert.ok(!foo.removeListener("x", l)); foo.notify({ id: "x", value: 2 }); assert.deepEqual(res, { x: 1, [EVENT_ALL]: 2 }); - assert.doesNotThrow(() => foo.removeListener(EVENT_ALL, lall)); + assert.ok(foo.removeListener(EVENT_ALL, lall)); + assert.ok((foo)._listeners[EVENT_ALL] === undefined); + assert.deepEqual((foo)._listeners, {}); foo.notify({ id: "x", value: 3 }); assert.deepEqual(res, { x: 1, [EVENT_ALL]: 2 }); }); diff --git a/packages/arrays/CHANGELOG.md b/packages/arrays/CHANGELOG.md index d6b0d093c7..5de6a7d78d 100644 --- a/packages/arrays/CHANGELOG.md +++ b/packages/arrays/CHANGELOG.md @@ -3,6 +3,43 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/arrays@0.2.5...@thi.ng/arrays@0.3.0) (2019-11-09) + + +### Features + +* **arrays:** add isSorted() ([65b29f4](https://github.com/thi-ng/umbrella/commit/65b29f487459c535acdbed3890c8a4e27d87ae2c)) +* **arrays:** add shuffleRange(), refactor shuffle(), add tests ([1924a05](https://github.com/thi-ng/umbrella/commit/1924a05ea093e3d1d0b3f063cb331b330cee0c0a)) +* **arrays:** add types, quickSort(), swap(), multiSwap(), update readme ([b834722](https://github.com/thi-ng/umbrella/commit/b83472237b3ba262dcbb644c8ccc516d0021bc84)) + + + + + +## [0.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/arrays@0.2.4...@thi.ng/arrays@0.2.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/arrays + + + + + +## [0.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/arrays@0.2.3...@thi.ng/arrays@0.2.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/arrays + + + + + +## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/arrays@0.2.2...@thi.ng/arrays@0.2.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/arrays + + + + + ## [0.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/arrays@0.2.1...@thi.ng/arrays@0.2.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/arrays diff --git a/packages/arrays/README.md b/packages/arrays/README.md index 1a1b2c16bc..f9ee17d2d3 100644 --- a/packages/arrays/README.md +++ b/packages/arrays/README.md @@ -51,9 +51,14 @@ import * as a from "@thi.ng/arrays"; - [ensureArray()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/ensure-array.ts) - [ensureIterable()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/ensure-iterable.ts) - [fuzzyMatch()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/fuzzy-match.ts) +- [isSorted()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/is-sorted.ts) +- [multiSwap()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/swap.ts) - [peek()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/peek.ts) +- [quickSort()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/quicksort.ts) - [shuffle()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/shuffle.ts) (w/ custom PRNG support) +- [shuffleRange()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/shuffle.ts) (w/ custom PRNG support) - [startsWith()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/starts-with.ts) +- [swap()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/swap.ts) - [swizzle()](https://github.com/thi-ng/umbrella/tree/master/packages/arrays/src/swizzle.ts) ## Authors diff --git a/packages/arrays/package.json b/packages/arrays/package.json index 3690bd3106..deb50867be 100644 --- a/packages/arrays/package.json +++ b/packages/arrays/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/arrays", - "version": "0.2.2", + "version": "0.3.0", "description": "Array / Arraylike utilities", "module": "./index.js", "main": "./lib/index.js", @@ -29,16 +29,16 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/compare": "^1.0.9", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/random": "^1.1.10" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/compare": "^1.0.10", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/random": "^1.1.13" }, "keywords": [ "arrays", diff --git a/packages/arrays/src/api.ts b/packages/arrays/src/api.ts new file mode 100644 index 0000000000..92f01e6389 --- /dev/null +++ b/packages/arrays/src/api.ts @@ -0,0 +1,5 @@ +import { Fn3, TypedArray } from "@thi.ng/api"; + +export type AnyArray = any[] | TypedArray; + +export type SwapFn = Fn3; diff --git a/packages/arrays/src/binary-search.ts b/packages/arrays/src/binary-search.ts index b0ab4434ca..ec1887d877 100644 --- a/packages/arrays/src/binary-search.ts +++ b/packages/arrays/src/binary-search.ts @@ -3,7 +3,14 @@ import { compare } from "@thi.ng/compare"; /** * Returns the supposed index of `x` in pre-sorted array-like collection - * `buf`. If `x` can't be found, returns `-index-1`. + * `buf`. If `x` can't be found, returns `-index-1`, representing the + * negative of the index were `x` to be inserted into `buf`. E.g if the + * return value is -3, `x` would appear/insert at index 2. + * + * ``` + * binarySearch([2, 4, 6], 5); + * // -3 + * ``` * * The optional `key` function is used to obtain the actual sort value * of `x` and each array item (default: identity). diff --git a/packages/arrays/src/ends-with.ts b/packages/arrays/src/ends-with.ts index 8a26bf0fa8..710908c92c 100644 --- a/packages/arrays/src/ends-with.ts +++ b/packages/arrays/src/ends-with.ts @@ -1,5 +1,18 @@ import { equiv as _eq } from "@thi.ng/equiv"; +/** + * Returns true if the last items of `buf` are the same items as in + * `needle`. This means `buf` should have at least the same length as + * `needle` for this to be true. + * + * By default, uses thi.ng/equiv for equality checking. + * + * @see startsWith + * + * @param buf + * @param needle + * @param equiv + */ export const endsWith = ( buf: ArrayLike, needle: ArrayLike, diff --git a/packages/arrays/src/ensure-array.ts b/packages/arrays/src/ensure-array.ts index e0d15a1cc3..d68a5df244 100644 --- a/packages/arrays/src/ensure-array.ts +++ b/packages/arrays/src/ensure-array.ts @@ -12,5 +12,12 @@ import { ensureIterable } from "./ensure-iterable"; export const ensureArray = (x: any): any[] => isArray(x) ? x : [...ensureIterable(x)]; +/** + * Similar to `ensureArray()`, but for `ArrayLike` types. + * + * @see ensureArray + * + * @param x + */ export const ensureArrayLike = (x: any): ArrayLike => isArrayLike(x) ? x : [...ensureIterable(x)]; diff --git a/packages/arrays/src/ensure-iterable.ts b/packages/arrays/src/ensure-iterable.ts index 3784d62fb0..79e1fd2222 100644 --- a/packages/arrays/src/ensure-iterable.ts +++ b/packages/arrays/src/ensure-iterable.ts @@ -1,8 +1,13 @@ import { illegalArgs } from "@thi.ng/errors"; -export const ensureIterable = (x: any): IterableIterator => { - if (!(x != null && x[Symbol.iterator])) { +/** + * Attempts to obtain an iterator from `x` and throws error if `x` is + * not iterable. + * + * @param x + */ +export const ensureIterable = (x: any): Iterable => { + (x == null || !x[Symbol.iterator]) && illegalArgs(`value is not iterable: ${x}`); - } return x; }; diff --git a/packages/arrays/src/index.ts b/packages/arrays/src/index.ts index fa06b5b5a9..ea9d7e854b 100644 --- a/packages/arrays/src/index.ts +++ b/packages/arrays/src/index.ts @@ -4,7 +4,10 @@ export * from "./ensure-array"; export * from "./ensure-iterable"; export * from "./find"; export * from "./fuzzy-match"; +export * from "./is-sorted"; export * from "./peek"; +export * from "./quicksort"; export * from "./shuffle"; export * from "./starts-with"; +export * from "./swap"; export * from "./swizzle"; diff --git a/packages/arrays/src/is-sorted.ts b/packages/arrays/src/is-sorted.ts new file mode 100644 index 0000000000..194ca9bc72 --- /dev/null +++ b/packages/arrays/src/is-sorted.ts @@ -0,0 +1,38 @@ +import { Comparator } from "@thi.ng/api"; +import { compare } from "@thi.ng/compare"; + +/** + * Returns true if the given array and its elements in the selected + * index range (entire array, by default) are in the order defined by + * the given comparator (thi.ng/compare by default). Always returns + * true, if effective index range (or array length) has less than two + * elements. No bounds checking. + * + * ``` + * isSorted([3, 2, 1]) + * // false + * + * // w/ custom comparator + * isSorted([3, 2, 1], (a, b) => b - a) + * // true + * ``` + * + * @param arr + * @param cmp + * @param start + * @param end + */ +export const isSorted = ( + arr: ArrayLike, + cmp: Comparator = compare, + start = 0, + end = arr.length +) => { + let prev = arr[start]; + while (++start < end) { + const curr = arr[start]; + if (cmp(prev, curr) > 0) return false; + prev = curr; + } + return true; +}; diff --git a/packages/arrays/src/quicksort.ts b/packages/arrays/src/quicksort.ts new file mode 100644 index 0000000000..68edf29fd1 --- /dev/null +++ b/packages/arrays/src/quicksort.ts @@ -0,0 +1,64 @@ +import { Comparator, Fn3, TypedArray } from "@thi.ng/api"; +import { compare } from "@thi.ng/compare"; +import { swap } from "./swap"; + +/** + * In-place quicksort implementation with optional comparator & index + * based swap function, useful for sorting multiple related arrays in + * parallel, based on a single sort criteria. Supports sorting of + * sub-ranges only, via optionally given `start`/`end` indices (both + * inclusive). + * + * Uses Hoare partitioning scheme. thi.ng/compare is used as default + * comparator and `swap` from this package as default swap function. + * + * https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme + * + * ``` + * a = [4, 3, 1, 8, 5] + * b = [40, 30, 10, 80, 50] + * c = [-4, -3, -1, -8, -5] + * + * // use `multiSwap` to sort extra arrays based on sort order of `a` + * quickSort(a, undefined, multiSwap(b, c)) + * // [ 1, 3, 4, 5, 8 ] (a) + * + * b + * // [ 10, 30, 40, 50, 80 ] + * c + * // [ -1, -3, -4, -5, -8 ] + * ``` + * + * @param arr + * @param _cmp + * @param _swap + * @param start + * @param end + */ +// prettier-ignore +export function quickSort(arr: T[], _cmp?: Comparator, _swap?: Fn3, start?: number, end?: number): T[]; +// prettier-ignore +export function quickSort(arr: T, _cmp?: Comparator, _swap?: Fn3, start?: number, end?: number): T; +// prettier-ignore +export function quickSort(arr: any, _cmp: Comparator = compare, _swap: Fn3 = swap, start = 0, end = arr.length - 1): any { + if (start < end) { + const pivot = arr[start + ((end - start) >> 1)]; + let s = start - 1; + let e = end + 1; + + while (true) { + do { + s++; + } while (_cmp(arr[s], pivot) < 0); + do { + e--; + } while (_cmp(arr[e], pivot) > 0); + if (s >= e) break; + _swap(arr, s, e); + } + + quickSort(arr, _cmp, _swap, start, e); + quickSort(arr, _cmp, _swap, e + 1, end); + } + return arr; +} diff --git a/packages/arrays/src/shuffle.ts b/packages/arrays/src/shuffle.ts index f181c49a4d..9771da5995 100644 --- a/packages/arrays/src/shuffle.ts +++ b/packages/arrays/src/shuffle.ts @@ -1,23 +1,32 @@ +import { assert, TypedArray } from "@thi.ng/api"; import { IRandom, SYSTEM } from "@thi.ng/random"; +import { AnyArray } from "./api"; /** - * Shuffles the first `n` items of given array, using Fisher-yates and - * optional `rnd` PRNG. If `n` is `undefined`, the entire array will be - * shuffled. - * + * Shuffles the items in the given index range of array `buf` using + * Fisher-yates and optional `rnd` PRNG. If neither `start` / `end` are + * given, the entire array will be shuffled. Mutates original array. * * @param buf * @param n * @param rnd */ -export const shuffle = (buf: any[], n = buf.length, rnd: IRandom = SYSTEM) => { - n = Math.min(n, buf.length); +export const shuffleRange = ( + buf: T, + start = 0, + end = buf.length, + rnd: IRandom = SYSTEM +) => { + assert( + start >= 0 && end >= start && end <= buf.length, + `illegal range ${start}..${end}` + ); + let n = end - start; const l = n; if (l > 1) { - n = Math.min(n, l); while (--n >= 0) { - const a = rnd.float(l) | 0; - const b = rnd.float(l) | 0; + const a = (start + rnd.float(l)) | 0; + const b = (start + rnd.float(l)) | 0; const t = buf[a]; buf[a] = buf[b]; buf[b] = t; @@ -25,3 +34,19 @@ export const shuffle = (buf: any[], n = buf.length, rnd: IRandom = SYSTEM) => { } return buf; }; + +/** + * Applies `shuffleRange()` to the given array. If `n` is given, only + * the first `n` items are shuffled. Mutates original array. + * + * @see shuffleRange + * + * @param buf + * @param n + * @param rnd + */ +export const shuffle = ( + buf: T, + n = buf.length, + rnd: IRandom = SYSTEM +) => shuffleRange(buf, 0, n, rnd); diff --git a/packages/arrays/src/starts-with.ts b/packages/arrays/src/starts-with.ts index 84de2df879..e49f792dd4 100644 --- a/packages/arrays/src/starts-with.ts +++ b/packages/arrays/src/starts-with.ts @@ -1,5 +1,18 @@ import { equiv as _eq } from "@thi.ng/equiv"; +/** + * Returns true if the first items of `buf` are the same items as in + * `needle`. This means `buf` should have at least the same length as + * `needle` for this to be true. + * + * By default, uses thi.ng/equiv for equality checking. + * + * @see endsWith + * + * @param buf + * @param needle + * @param equiv + */ export const startsWith = ( buf: ArrayLike, needle: ArrayLike, diff --git a/packages/arrays/src/swap.ts b/packages/arrays/src/swap.ts new file mode 100644 index 0000000000..bc25d149ec --- /dev/null +++ b/packages/arrays/src/swap.ts @@ -0,0 +1,68 @@ +import { AnyArray, SwapFn } from "./api"; + +/** + * Swaps values at index `x`/`y` in given array. + * + * @param arr + * @param x + * @param y + */ +export const swap = (arr: AnyArray, x: number, y: number) => { + const t = arr[x]; + arr[x] = arr[y]; + arr[y] = t; +}; + +/** + * Higher-order version of `swap` for swapping elements in multiple + * arrays at once. The returned function takes the same args as `swap`, + * and when called swaps 2 elements in the array given to that function + * AND in the arrays given to `multiSwap` itself. Provides fast routes + * for up to 3 extra arrays, then falls back to a loop-based approach. + * + * ``` + * a = [2, 1]; + * b = [20, 10]; + * c = [40, 30]; + * + * ms = multiSwap(b, c); + * ms(a, 0, 1); + * + * // a: [1, 2] + * // b: [10, 20] + * // c: [30, 40] + * ``` + * + * @param xs + */ +export const multiSwap = (...xs: AnyArray[]): SwapFn => { + const [b, c, d] = xs; + const n = xs.length; + switch (n) { + case 0: + return swap; + case 1: + return (a, x, y) => { + swap(a, x, y); + swap(b, x, y); + }; + case 2: + return (a, x, y) => { + swap(a, x, y); + swap(b, x, y); + swap(c, x, y); + }; + case 3: + return (a, x, y) => { + swap(a, x, y); + swap(b, x, y); + swap(c, x, y); + swap(d, x, y); + }; + default: + return (a, x, y) => { + swap(a, x, y); + for (let i = n; --i >= 0; ) swap(xs[i], x, y); + }; + } +}; diff --git a/packages/arrays/test/shuffle.ts b/packages/arrays/test/shuffle.ts new file mode 100644 index 0000000000..873d8bd8e2 --- /dev/null +++ b/packages/arrays/test/shuffle.ts @@ -0,0 +1,27 @@ +import { XsAdd } from "@thi.ng/random"; +import * as assert from "assert"; +import { shuffle, shuffleRange } from "../src"; + +describe("arrays", () => { + it("shuffle", () => { + const src = "abcdefghijklmnopqrstuvwxyz"; + const buf = [...src]; + assert.equal(shuffleRange(buf, 0, 0).join(""), src); + assert.equal(shuffleRange(buf, 0, 1).join(""), src); + assert.equal(shuffle(buf, 0).join(""), src); + assert.equal(shuffle(buf, 1).join(""), src); + assert.throws(() => shuffleRange(buf, -1)); + assert.throws(() => shuffleRange(buf, 100)); + assert.throws(() => shuffleRange(buf, 1, 0)); + assert.throws(() => shuffleRange(buf, 0, 100)); + const rnd = new XsAdd(0xdeadbeef); + assert.equal( + shuffleRange(buf, 10, 20, rnd).join(""), + "abcdefghijnokpsqmtrluvwxyz" + ); + assert.equal( + shuffle(buf, buf.length, rnd).join(""), + "ovcwfhbnizgyekuqrdjslxpatm" + ); + }); +}); diff --git a/packages/associative/CHANGELOG.md b/packages/associative/CHANGELOG.md index 81a7c69ea7..c50cc9c736 100644 --- a/packages/associative/CHANGELOG.md +++ b/packages/associative/CHANGELOG.md @@ -3,6 +3,62 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.1.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@3.0.1...@thi.ng/associative@3.1.0) (2019-11-09) + + +### Bug Fixes + +* **associative:** fix off-by-one error in sparseSet() factory, add tests ([94ff308](https://github.com/thi-ng/umbrella/commit/94ff3089d7c24627e57c731d57ab048ca1eff5b1)) + + +### Features + +* **associative:** add reducer versions of difference, intersection, union ([058b9d3](https://github.com/thi-ng/umbrella/commit/058b9d38a1fe25ee4e09dde1ed3f9a52831a4769)) + + + + + +## [3.0.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@3.0.0...@thi.ng/associative@3.0.1) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/associative + + + + + +# [3.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@2.4.3...@thi.ng/associative@3.0.0) (2019-08-21) + + +### Code Refactoring + +* **associative:** update XXXMap.dissoc() signature to unify API ([632c57a](https://github.com/thi-ng/umbrella/commit/632c57a)) + + +### BREAKING CHANGES + +* **associative:** dissoc() method signature changed from varargs to `Iterable` + +Example: + +- previously: `HashMap.dissoc(1, 2, 3)` +- now: `HashMap.dissoc([1, 2, 3])` + +This new signature is the same as used by `dissoc()` standalone fn and +the `disj()` methods of the various Sets in this package. + + + + + +## [2.4.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@2.4.2...@thi.ng/associative@2.4.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/associative + + + + + ## [2.4.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@2.4.1...@thi.ng/associative@2.4.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/associative diff --git a/packages/associative/README.md b/packages/associative/README.md index 39892feba0..b37d91eaca 100644 --- a/packages/associative/README.md +++ b/packages/associative/README.md @@ -27,6 +27,7 @@ operations working with these types: implementations) - getters w/ optional "not-found" default value - `fromObject()` converters (for maps only) +- `SparseSet` implementations for numeric values - Polymorphic set operations (union, intersection, difference) - works with both native and custom Sets and retains their types - Natural & selective @@ -236,12 +237,47 @@ this (and other) package(s). This set uses a `SortedMap` as backing store. +### SparseSet8/16/32 + +[Sparse sets](https://research.swtch.com/sparse) provide super fast +(approx. 4x faster than the native `Set` impl) insertion & lookups for +numeric values in the interval `[0..n)` . The implementation in this +package provides most of the ES6 Set API and internally relies on 2 uint +typed arrays, with the actual backing type dependent on `n`. + +Furthermore, unless (or until) values are being removed from the set, +they retain their original insertion order. For some use cases (e.g. +deduplication of values), this property can be very useful. + +```ts +// create sparse set for value range 0 - 99 (uint8 backed) +const a = sparseSet(100); +a.into([99, 42, 66, 23, 66, 42]); +// SparseSet8 { 99, 42, 66, 23 } + +a.has(66) +// true + +// sparse sets are iterable +[...a] +// [ 99, 42, 66, 23 ] + +// attempting to add out-of-range values will fail +a.add(100) +// SparseSet8 { 99, 42, 66, 23 } + +// create sparse set for 16 bit value range 0 - 0xffff (uint16 backed) +const b = sparseSet(0x10000); +// SparseSet16 {} +``` + ## Usage examples Please see these packages for use cases: - [@thi.ng/cache](https://github.com/thi-ng/umbrella/tree/master/packages/cache) - [@thi.ng/dgraph](https://github.com/thi-ng/umbrella/tree/master/packages/dgraph) +- [@thi.ng/ecs](https://github.com/thi-ng/umbrella/tree/master/packages/ecs) - [@thi.ng/rstream-query](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-query) ## Authors diff --git a/packages/associative/package.json b/packages/associative/package.json index 978f774b00..6586378aed 100644 --- a/packages/associative/package.json +++ b/packages/associative/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/associative", - "version": "2.4.2", + "version": "3.1.0", "description": "Alternative Set & Map data type implementations with customizable equality semantics & supporting operations", "module": "./index.js", "main": "./lib/index.js", @@ -29,18 +29,18 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/binary": "^1.1.0", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/compare": "^1.0.9", - "@thi.ng/dcons": "^2.1.2", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/binary": "^1.1.1", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/compare": "^1.0.10", + "@thi.ng/dcons": "^2.1.6", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "data structures", @@ -57,6 +57,7 @@ "skiplist", "sorted map", "sorted set", + "sparse set", "typescript", "union" ], diff --git a/packages/associative/src/api.ts b/packages/associative/src/api.ts index 1d2b9d2996..54ff5ea2fb 100644 --- a/packages/associative/src/api.ts +++ b/packages/associative/src/api.ts @@ -40,14 +40,20 @@ export interface EquivMapOpts extends EquivSetOpts { keys: EquivSetConstructor; } +/** + * Hash function for key values of type K + */ +export type HashFn = Fn; + /** * Creation options for HashMap class. */ export interface HashMapOpts { /** - * Function for computing key hash codes. MUST be supplied. + * Function for computing key hash codes. MUST be supplied. Only + * numeric hashes are supported. */ - hash: Fn; + hash: HashFn; /** * Optional key equality predicate. Default: thi.ng/equiv */ diff --git a/packages/associative/src/array-set.ts b/packages/associative/src/array-set.ts index a908aec9be..f3ccb5e9d7 100644 --- a/packages/associative/src/array-set.ts +++ b/packages/associative/src/array-set.ts @@ -6,6 +6,9 @@ import { } from "@thi.ng/api"; import { equiv } from "@thi.ng/equiv"; import { EquivSetOpts, IEquivSet } from "./api"; +import { dissoc } from "./dissoc"; +import { equivSet } from "./internal/equiv"; +import { into } from "./into"; interface ArraySetProps { vals: T[]; @@ -14,6 +17,8 @@ interface ArraySetProps { const __private = new WeakMap, ArraySetProps>(); +const __vals = (inst: ArraySet) => __private.get(inst)!.vals; + /** * An alternative set implementation to the native ES6 Set type. Uses * customizable equality/equivalence predicate and so is more useful @@ -34,7 +39,7 @@ export class ArraySet extends Set implements IEquivSet { } *[Symbol.iterator](): IterableIterator { - yield* __private.get(this)!.vals; + yield* __vals(this); } get [Symbol.species]() { @@ -46,7 +51,7 @@ export class ArraySet extends Set implements IEquivSet { } get size(): number { - return __private.get(this)!.vals.length; + return __vals(this).length; } copy(): ArraySet { @@ -61,56 +66,53 @@ export class ArraySet extends Set implements IEquivSet { } clear() { - __private.get(this)!.vals.length = 0; + __vals(this).length = 0; } first(): T | undefined { if (this.size) { - return __private.get(this)!.vals[0]; + return __vals(this)[0]; } } - add(x: T) { - !this.has(x) && __private.get(this)!.vals.push(x); + add(key: T) { + !this.has(key) && __vals(this).push(key); return this; } - into(xs: Iterable) { - for (let x of xs) { - this.add(x); - } - return this; + into(keys: Iterable) { + return into(this, keys); } - has(x: T) { - return this.get(x, SEMAPHORE) !== SEMAPHORE; + has(key: T) { + return this.get(key, SEMAPHORE) !== SEMAPHORE; } /** * Returns the canonical value for `x`, if present. If the set * contains no equivalent for `x`, returns `notFound`. * - * @param x + * @param key * @param notFound */ - get(x: T, notFound?: T): T | undefined { + get(key: T, notFound?: T): T | undefined { const $this = __private.get(this)!; const eq = $this.equiv; const vals = $this.vals; - for (let i = vals.length - 1; i >= 0; i--) { - if (eq(vals[i], x)) { + for (let i = vals.length; --i >= 0; ) { + if (eq(vals[i], key)) { return vals[i]; } } return notFound; } - delete(x: T) { + delete(key: T) { const $this = __private.get(this)!; const eq = $this.equiv; const vals = $this.vals; - for (let i = vals.length - 1; i >= 0; i--) { - if (eq(vals[i], x)) { + for (let i = vals.length; --i >= 0; ) { + if (eq(vals[i], key)) { vals.splice(i, 1); return true; } @@ -118,34 +120,16 @@ export class ArraySet extends Set implements IEquivSet { return false; } - disj(xs: Iterable) { - for (let x of xs) { - this.delete(x); - } - return this; + disj(keys: Iterable) { + return dissoc(this, keys); } equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Set)) { - return false; - } - if (this.size !== o.size) { - return false; - } - const vals = __private.get(this)!.vals; - for (let i = vals.length; --i >= 0; ) { - if (!o.has(vals[i])) { - return false; - } - } - return true; + return equivSet(this, o); } forEach(fn: Fn3, Readonly, Set, void>, thisArg?: any) { - const vals = __private.get(this)!.vals; + const vals = __vals(this); for (let i = vals.length; --i >= 0; ) { const v = vals[i]; fn.call(thisArg, v, v, this); @@ -153,17 +137,17 @@ export class ArraySet extends Set implements IEquivSet { } *entries(): IterableIterator> { - for (let v of __private.get(this)!.vals) { + for (let v of __vals(this)) { yield [v, v]; } } *keys(): IterableIterator { - yield* __private.get(this)!.vals; + yield* __vals(this); } *values(): IterableIterator { - yield* this.keys(); + yield* __vals(this); } opts(): EquivSetOpts { diff --git a/packages/associative/src/difference.ts b/packages/associative/src/difference.ts index 62ad7f8143..1cd3368b37 100644 --- a/packages/associative/src/difference.ts +++ b/packages/associative/src/difference.ts @@ -1,3 +1,5 @@ +import { Reducer } from "@thi.ng/transducers"; +import { xformSetOp } from "./internal/xform-setop"; import { into } from "./into"; import { copy, empty } from "./utils"; @@ -20,3 +22,16 @@ export const difference = (a: Set, b: Set, out?: Set): Set => { } return out!; }; + +/** + * Reducer version of `difference`. If `src` is given returns the + * reduced difference of given inputs, else merely returns a reducer + * to be used with thi.ng/transducers `reduce` / `transduce` functions. + * + * @param src + */ +export function differenceR(): Reducer, Iterable>; +export function differenceR(src: Iterable): Set; +export function differenceR(src?: Iterable>) { + return xformSetOp(differenceR, difference, src); +} diff --git a/packages/associative/src/dissoc.ts b/packages/associative/src/dissoc.ts new file mode 100644 index 0000000000..c55abf645c --- /dev/null +++ b/packages/associative/src/dissoc.ts @@ -0,0 +1,20 @@ +import { IObjectOf } from "@thi.ng/api"; + +export function dissoc(map: Map, keys: Iterable): Map; +export function dissoc(set: Set, keys: Iterable): Set; +export function dissoc(coll: Map | Set, keys: Iterable) { + for (let k of keys) { + coll.delete(k); + } + return coll; +} + +export const dissocObj = ( + obj: IObjectOf, + keys: Iterable +) => { + for (let k of keys) { + delete obj[k]; + } + return obj; +}; diff --git a/packages/associative/src/equiv-map.ts b/packages/associative/src/equiv-map.ts index 82bfdf6841..753027e765 100644 --- a/packages/associative/src/equiv-map.ts +++ b/packages/associative/src/equiv-map.ts @@ -10,6 +10,9 @@ import { import { equiv } from "@thi.ng/equiv"; import { EquivMapOpts, IEquivSet } from "./api"; import { ArraySet } from "./array-set"; +import { dissoc } from "./dissoc"; +import { equivMap } from "./internal/equiv"; +import { into } from "./into"; interface MapProps { keys: IEquivSet; @@ -19,6 +22,8 @@ interface MapProps { const __private = new WeakMap, MapProps>(); +const __map = (map: EquivMap) => __private.get(map)!.map; + export class EquivMap extends Map implements Iterable>, @@ -110,21 +115,7 @@ export class EquivMap extends Map } equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Map)) { - return false; - } - if (this.size !== o.size) { - return false; - } - for (let p of __private.get(this)!.map.entries()) { - if (!equiv(o.get(p[0]), p[1])) { - return false; - } - } - return true; + return equivMap(this, o); } delete(key: K) { @@ -138,15 +129,12 @@ export class EquivMap extends Map return false; } - dissoc(...keys: K[]) { - for (let k of keys) { - this.delete(k); - } - return this; + dissoc(keys: Iterable) { + return dissoc(this, keys); } forEach(fn: Fn3, Map, void>, thisArg?: any) { - for (let pair of __private.get(this)!.map) { + for (let pair of __map(this)) { fn.call(thisArg, pair[1], pair[0], this); } } @@ -177,22 +165,19 @@ export class EquivMap extends Map } into(pairs: Iterable>) { - for (let p of pairs) { - this.set(p[0], p[1]); - } - return this; + return into(this, pairs); } entries(): IterableIterator> { - return __private.get(this)!.map.entries(); + return __map(this).entries(); } keys(): IterableIterator { - return __private.get(this)!.map.keys(); + return __map(this).keys(); } values(): IterableIterator { - return __private.get(this)!.map.values(); + return __map(this).values(); } opts(): EquivMapOpts { diff --git a/packages/associative/src/hash-map.ts b/packages/associative/src/hash-map.ts index 14bffc14a8..21a76a4b0e 100644 --- a/packages/associative/src/hash-map.ts +++ b/packages/associative/src/hash-map.ts @@ -10,6 +10,9 @@ import { import { ceilPow2 } from "@thi.ng/binary"; import { equiv } from "@thi.ng/equiv"; import { HashMapOpts } from "./api"; +import { dissoc } from "./dissoc"; +import { equivMap } from "./internal/equiv"; +import { into } from "./into"; interface HashMapState { hash: Fn; @@ -22,12 +25,19 @@ interface HashMapState { const __private = new WeakMap, HashMapState>(); +const __iterator = (map: HashMap, id: 0 | 1) => + function*() { + for (let p of __private.get(map)!.bins) { + if (p) yield p[id]; + } + }; + const DEFAULT_CAP = 16; /** - * Configurable hash map implementation w/ ES6 Map API and using open + * Configurable hash map implementation w/ ES6 Map API. Uses open * addressing / linear probing to resolve key collisions. Supports any - * key types, via user supplied hash function. + * key types via mandatory user supplied hash function. * * See `HashMapOpts` for further configuration & behavior details. * @@ -87,16 +97,12 @@ export class HashMap extends Map } } - *keys(): IterableIterator { - for (let p of __private.get(this)!.bins) { - if (p) yield p[0]; - } + keys(): IterableIterator { + return __iterator(this, 0)(); } - *values(): IterableIterator { - for (let p of __private.get(this)!.bins) { - if (p) yield p[1]; - } + values(): IterableIterator { + return __iterator(this, 1)(); } forEach(fn: Fn3, Map, void>, thisArg?: any) { @@ -128,21 +134,7 @@ export class HashMap extends Map } equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Map)) { - return false; - } - if (this.size !== o.size) { - return false; - } - for (let p of __private.get(this)!.bins) { - if (p && !equiv(o.get(p[0]), p[1])) { - return false; - } - } - return true; + return equivMap(this, o); } has(key: K): boolean { @@ -197,17 +189,11 @@ export class HashMap extends Map } into(pairs: Iterable>) { - for (let p of pairs) { - this.set(p[0], p[1]); - } - return this; + return into(this, pairs); } - dissoc(...keys: K[]) { - for (let k of keys) { - this.delete(k); - } - return this; + dissoc(keys: Iterable) { + return dissoc(this, keys); } opts(overrides?: Partial>): HashMapOpts { diff --git a/packages/associative/src/index.ts b/packages/associative/src/index.ts index 85c818f47c..bb46785349 100644 --- a/packages/associative/src/index.ts +++ b/packages/associative/src/index.ts @@ -1,6 +1,7 @@ export * from "./array-set"; export * from "./common-keys"; export * from "./difference"; +export * from "./dissoc"; export * from "./equiv-map"; export * from "./hash-map"; export * from "./indexed"; diff --git a/packages/associative/src/internal/equiv.ts b/packages/associative/src/internal/equiv.ts new file mode 100644 index 0000000000..cd02679f61 --- /dev/null +++ b/packages/associative/src/internal/equiv.ts @@ -0,0 +1,31 @@ +import { equiv } from "@thi.ng/equiv"; + +export const equivMap = (a: Map, b: any) => { + if (a === b) { + return true; + } + if (!(b instanceof Map) || a.size !== b.size) { + return false; + } + for (let p of a.entries()) { + if (!equiv(b.get(p[0]), p[1])) { + return false; + } + } + return true; +}; + +export const equivSet = (a: Set, b: any) => { + if (a === b) { + return true; + } + if (!(b instanceof Set) || a.size !== b.size) { + return false; + } + for (let k of a.keys()) { + if (!b.has(k)) { + return false; + } + } + return true; +}; diff --git a/packages/associative/src/internal/xform-setop.ts b/packages/associative/src/internal/xform-setop.ts new file mode 100644 index 0000000000..a432684d75 --- /dev/null +++ b/packages/associative/src/internal/xform-setop.ts @@ -0,0 +1,16 @@ +import { Fn0 } from "@thi.ng/api"; +import { reduce, Reducer } from "@thi.ng/transducers"; +import { ensureSet } from "../utils"; + +export const xformSetOp = ( + rfn: Fn0, Iterable>>, + op: (a: Set, b: Set, c?: Set) => Set, + src?: Iterable> +) => + src + ? reduce(rfn(), src) + : , Iterable>>[ + () => null, + (acc) => acc || new Set(), + (acc, x) => (!acc ? ensureSet(x) : op(acc, ensureSet(x))) + ]; diff --git a/packages/associative/src/intersection.ts b/packages/associative/src/intersection.ts index 8fe4431d74..b11f23c395 100644 --- a/packages/associative/src/intersection.ts +++ b/packages/associative/src/intersection.ts @@ -1,3 +1,5 @@ +import { Reducer } from "@thi.ng/transducers"; +import { xformSetOp } from "./internal/xform-setop"; import { into } from "./into"; import { empty } from "./utils"; @@ -26,3 +28,16 @@ export const intersection = (a: Set, b: Set, out?: Set): Set => { } return out!; }; + +/** + * Reducer version of `intersection`. If `src` is given returns the + * reduced intersection of given inputs, else merely returns a reducer + * to be used with thi.ng/transducers `reduce` / `transduce` functions. + * + * @param src + */ +export function intersectionR(): Reducer, Iterable>; +export function intersectionR(src: Iterable): Set; +export function intersectionR(src?: Iterable>) { + return xformSetOp(intersectionR, intersection, src); +} diff --git a/packages/associative/src/into.ts b/packages/associative/src/into.ts index c7aac008b7..e68ae0d576 100644 --- a/packages/associative/src/into.ts +++ b/packages/associative/src/into.ts @@ -7,10 +7,8 @@ import { isMap } from "@thi.ng/checks"; * @param dest * @param src */ -export function into( - dest: Map, - src: Iterable> -): Map; +// prettier-ignore +export function into(dest: Map, src: Iterable>): Map; export function into(dest: Set, src: Iterable): Set; export function into(dest: Map | Set, src: Iterable) { if (isMap(dest)) { diff --git a/packages/associative/src/ll-set.ts b/packages/associative/src/ll-set.ts index 7a4a418c7e..700db7bbab 100644 --- a/packages/associative/src/ll-set.ts +++ b/packages/associative/src/ll-set.ts @@ -7,6 +7,9 @@ import { import { DCons } from "@thi.ng/dcons"; import { equiv } from "@thi.ng/equiv"; import { EquivSetOpts, IEquivSet } from "./api"; +import { dissoc } from "./dissoc"; +import { equivSet } from "./internal/equiv"; +import { into } from "./into"; interface SetProps { vals: DCons; @@ -15,6 +18,8 @@ interface SetProps { const __private = new WeakMap, SetProps>(); +const __vals = (inst: LLSet) => __private.get(inst)!.vals; + /** * Similar to `ArraySet`, this class is an alternative implementation of * the native ES6 Set API using a @thi.ng/dcons linked list as backing @@ -38,7 +43,7 @@ export class LLSet extends Set implements IEquivSet { } *[Symbol.iterator](): IterableIterator { - yield* __private.get(this)!.vals; + yield* __vals(this); } get [Symbol.species]() { @@ -50,7 +55,7 @@ export class LLSet extends Set implements IEquivSet { } get size(): number { - return __private.get(this)!.vals.length; + return __vals(this).length; } copy() { @@ -65,44 +70,41 @@ export class LLSet extends Set implements IEquivSet { } clear() { - __private.get(this)!.vals.clear(); + __vals(this).clear(); } first(): T | undefined { if (this.size) { - return __private.get(this)!.vals.head!.value; + return __vals(this).head!.value; } } - add(x: T) { - !this.has(x) && __private.get(this)!.vals.push(x); + add(key: T) { + !this.has(key) && __vals(this).push(key); return this; } - into(xs: Iterable) { - for (let x of xs) { - this.add(x); - } - return this; + into(keys: Iterable) { + return into(this, keys); } - has(x: T) { - return this.get(x, SEMAPHORE) !== SEMAPHORE; + has(key: T) { + return this.get(key, SEMAPHORE) !== SEMAPHORE; } /** - * Returns the canonical value for `x`, if present. If the set - * contains no equivalent for `x`, returns `notFound`. + * Returns the canonical (stored) value for `key`, if present. If + * the set contains no equivalent for `key`, returns `notFound`. * - * @param x + * @param key * @param notFound */ - get(x: T, notFound?: T): T | undefined { + get(key: T, notFound?: T): T | undefined { const $this = __private.get(this)!; const eq = $this.equiv; let i = $this.vals.head; while (i) { - if (eq(i.value, x)) { + if (eq(i.value, key)) { return i.value; } i = i.next; @@ -110,12 +112,12 @@ export class LLSet extends Set implements IEquivSet { return notFound; } - delete(x: T) { + delete(key: T) { const $this = __private.get(this)!; const eq = $this.equiv; let i = $this.vals.head; while (i) { - if (eq(i.value, x)) { + if (eq(i.value, key)) { $this.vals.splice(i, 1); return true; } @@ -124,35 +126,16 @@ export class LLSet extends Set implements IEquivSet { return false; } - disj(xs: Iterable) { - for (let x of xs) { - this.delete(x); - } - return this; + disj(keys: Iterable) { + return dissoc(this, keys); } equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Set)) { - return false; - } - if (this.size !== o.size) { - return false; - } - let i = __private.get(this)!.vals.head; - while (i) { - if (!o.has(i.value)) { - return false; - } - i = i.next; - } - return true; + return equivSet(this, o); } forEach(fn: Fn3, Readonly, Set, void>, thisArg?: any) { - let i = __private.get(this)!.vals.head; + let i = __vals(this).head; while (i) { fn.call(thisArg, i.value, i.value, this); i = i.next; @@ -160,17 +143,17 @@ export class LLSet extends Set implements IEquivSet { } *entries(): IterableIterator> { - for (let v of __private.get(this)!.vals) { + for (let v of __vals(this)) { yield [v, v]; } } *keys(): IterableIterator { - yield* __private.get(this)!.vals; + yield* __vals(this); } *values(): IterableIterator { - yield* this.keys(); + yield* __vals(this); } opts(): EquivSetOpts { diff --git a/packages/associative/src/sorted-map.ts b/packages/associative/src/sorted-map.ts index ffb88e8c04..b53513ba7b 100644 --- a/packages/associative/src/sorted-map.ts +++ b/packages/associative/src/sorted-map.ts @@ -6,9 +6,11 @@ import { SEMAPHORE } from "@thi.ng/api"; import { compare } from "@thi.ng/compare"; -import { equiv } from "@thi.ng/equiv"; import { isReduced, map, ReductionFn } from "@thi.ng/transducers"; import { SortedMapOpts } from "./api"; +import { dissoc } from "./dissoc"; +import { equivMap } from "./internal/equiv"; +import { into } from "./into"; interface SortedMapState { head: Node; @@ -161,10 +163,10 @@ export class SortedMap extends Map { let x: IteratorResult>, y: IteratorResult>; let c: number; while (((x = i.next()), (y = j.next()), !x.done && !y.done)) { - if ((c = compare(x.value[0], y.value[0])) !== 0) { - return c; - } - if ((c = compare(x.value[1], y.value[1])) !== 0) { + if ( + (c = compare(x.value[0], y.value[0])) !== 0 || + (c = compare(x.value[1], y.value[1])) !== 0 + ) { return c; } } @@ -172,21 +174,7 @@ export class SortedMap extends Map { } equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Map)) { - return false; - } - if (this.size !== o.size) { - return false; - } - for (let p of this.entries()) { - if (!equiv(o.get(p[0]), p[1])) { - return false; - } - } - return true; + return equivMap(this, o); } first(): Pair | undefined { @@ -272,17 +260,11 @@ export class SortedMap extends Map { } into(pairs: Iterable>) { - for (let p of pairs) { - this.set(p[0], p[1]); - } - return this; + return into(this, pairs); } - dissoc(...keys: K[]) { - for (let k of keys) { - this.delete(k); - } - return this; + dissoc(keys: Iterable) { + return dissoc(this, keys); } forEach(fn: Fn3, Map, void>, thisArg?: any) { diff --git a/packages/associative/src/sorted-set.ts b/packages/associative/src/sorted-set.ts index aaca4c4fcb..5e45b78f24 100644 --- a/packages/associative/src/sorted-set.ts +++ b/packages/associative/src/sorted-set.ts @@ -2,6 +2,9 @@ import { Fn3, ICompare, Pair } from "@thi.ng/api"; import { compare } from "@thi.ng/compare"; import { IReducible, map, ReductionFn } from "@thi.ng/transducers"; import { IEquivSet, SortedSetOpts } from "./api"; +import { dissoc } from "./dissoc"; +import { equivSet } from "./internal/equiv"; +import { into } from "./into"; import { SortedMap } from "./sorted-map"; const __private = new WeakMap, SortedMap>(); @@ -87,21 +90,7 @@ export class SortedSet extends Set } equiv(o: any) { - if (this === o) { - return true; - } - if (!(o instanceof Set)) { - return false; - } - if (this.size !== o.size) { - return false; - } - for (let k of this.keys()) { - if (!o.has(k)) { - return false; - } - } - return true; + return equivSet(this, o); } $reduce(rfn: ReductionFn, acc: any): any { @@ -120,16 +109,13 @@ export class SortedSet extends Set return __private.get(this)!.values(key, max); } - add(value: T) { - __private.get(this)!.set(value, value); + add(key: T) { + __private.get(this)!.set(key, key); return this; } - into(xs: Iterable) { - for (let x of xs) { - this.add(x); - } - return this; + into(keys: Iterable) { + return into(this, keys); } clear(): void { @@ -141,15 +127,12 @@ export class SortedSet extends Set return first ? first[0] : undefined; } - delete(value: T): boolean { - return __private.get(this)!.delete(value); + delete(key: T): boolean { + return __private.get(this)!.delete(key); } - disj(xs: Iterable) { - for (let x of xs) { - this.delete(x); - } - return this; + disj(keys: Iterable) { + return dissoc(this, keys); } forEach( @@ -161,12 +144,12 @@ export class SortedSet extends Set } } - has(value: T): boolean { - return __private.get(this)!.has(value); + has(key: T): boolean { + return __private.get(this)!.has(key); } - get(value: T, notFound?: T): T | undefined { - return __private.get(this)!.get(value, notFound); + get(key: T, notFound?: T): T | undefined { + return __private.get(this)!.get(key, notFound); } opts(): SortedSetOpts { diff --git a/packages/associative/src/sparse-set.ts b/packages/associative/src/sparse-set.ts index af7e96e51b..f4b08a4220 100644 --- a/packages/associative/src/sparse-set.ts +++ b/packages/associative/src/sparse-set.ts @@ -7,6 +7,8 @@ import { import { isNumber } from "@thi.ng/checks"; import { illegalArgs } from "@thi.ng/errors"; import { IEquivSet } from "./api"; +import { dissoc } from "./dissoc"; +import { into } from "./into"; interface SparseSetProps { dense: UIntArray; @@ -53,10 +55,7 @@ export abstract class ASparseSet extends Set if (this === o) { return true; } - if (!(o instanceof Set)) { - return false; - } - if (this.size !== o.size) { + if (!(o instanceof Set) || this.size !== o.size) { return false; } const $this = __private.get(this)!; @@ -69,27 +68,27 @@ export abstract class ASparseSet extends Set return true; } - add(k: number) { + add(key: number) { const $this = __private.get(this)!; const dense = $this.dense; const sparse = $this.sparse; const max = dense.length; - const i = sparse[k]; + const i = sparse[key]; const n = $this.n; - if (k < max && n < max && !(i < n && dense[i] === k)) { - dense[n] = k; - sparse[k] = n; + if (key < max && n < max && !(i < n && dense[i] === key)) { + dense[n] = key; + sparse[key] = n; $this.n++; } return this; } - delete(k: number) { + delete(key: number) { const $this = __private.get(this)!; const dense = $this.dense; const sparse = $this.sparse; - const i = sparse[k]; - if (i < $this.n && dense[i] === k) { + const i = sparse[key]; + if (i < $this.n && dense[i] === key) { const j = dense[--$this.n]; dense[i] = j; sparse[j] = i; @@ -98,14 +97,14 @@ export abstract class ASparseSet extends Set return false; } - has(k: number): boolean { + has(key: number): boolean { const $this = __private.get(this)!; - const i = $this.sparse[k]; - return i < $this.n && $this.dense[i] === k; + const i = $this.sparse[key]; + return i < $this.n && $this.dense[i] === key; } - get(k: number, notFound = -1) { - return this.has(k) ? k : notFound; + get(key: number, notFound = -1) { + return this.has(key) ? key : notFound; } first() { @@ -113,18 +112,12 @@ export abstract class ASparseSet extends Set return $this.n ? $this.dense[0] : undefined; } - into(ks: Iterable) { - for (let k of ks) { - this.add(k); - } - return this; + into(keys: Iterable) { + return into(this, keys); } - disj(ks: Iterable) { - for (let k of ks) { - this.delete(k); - } - return this; + disj(keys: Iterable) { + return dissoc(this, keys); } forEach(fn: Fn3, void>, thisArg?: any) { @@ -159,12 +152,13 @@ export abstract class ASparseSet extends Set return this.keys(); } - protected __copy(c: ASparseSet) { + protected __copyTo>(dest: S) { const $this = __private.get(this)!; - const $c = __private.get(c)!; + const $c = __private.get(dest)!; $c.dense = $this.dense.slice(); $c.sparse = $this.sparse.slice(); $c.n = $this.n; + return dest; } } @@ -189,9 +183,7 @@ export class SparseSet8 extends ASparseSet } copy() { - const c = new SparseSet8(0); - this.__copy(c); - return c; + return this.__copyTo(new SparseSet8(0)); } empty() { @@ -220,9 +212,7 @@ export class SparseSet16 extends ASparseSet } copy() { - const c = new SparseSet16(0); - this.__copy(c); - return c; + return this.__copyTo(new SparseSet16(0)); } empty() { @@ -251,9 +241,7 @@ export class SparseSet32 extends ASparseSet } copy() { - const c = new SparseSet8(0); - this.__copy(c); - return c; + return this.__copyTo(new SparseSet32(0)); } empty() { @@ -269,8 +257,8 @@ export class SparseSet32 extends ASparseSet * @param n */ export const sparseSet = (n: number) => - n < 0x100 + n <= 0x100 ? new SparseSet8(n) - : n < 0x10000 + : n <= 0x10000 ? new SparseSet16(n) : new SparseSet32(n); diff --git a/packages/associative/src/union.ts b/packages/associative/src/union.ts index 24ff0bc694..fdb4ae45ea 100644 --- a/packages/associative/src/union.ts +++ b/packages/associative/src/union.ts @@ -1,3 +1,5 @@ +import { Reducer } from "@thi.ng/transducers"; +import { xformSetOp } from "./internal/xform-setop"; import { into } from "./into"; import { copy } from "./utils"; @@ -10,6 +12,24 @@ import { copy } from "./utils"; * @param out */ export const union = (a: Set, b: Set, out?: Set): Set => { + if (a.size < b.size) { + const t = a; + a = b; + b = t; + } out = out ? into(out, a) : copy(a, Set); return a === b ? out! : into(out!, b); }; + +/** + * Reducer version of `union`. If `src` is given returns the + * reduced union of given inputs, else merely returns a reducer + * to be used with thi.ng/transducers `reduce` / `transduce` functions. + * + * @param src + */ +export function unionR(): Reducer, Iterable>; +export function unionR(src: Iterable): Set; +export function unionR(src?: Iterable>) { + return xformSetOp(unionR, union, src); +} diff --git a/packages/associative/test/sparse-set.ts b/packages/associative/test/sparse-set.ts index 739651d2ae..996a4f1d90 100644 --- a/packages/associative/test/sparse-set.ts +++ b/packages/associative/test/sparse-set.ts @@ -1,7 +1,12 @@ import { isSet } from "@thi.ng/checks"; import { equiv } from "@thi.ng/equiv"; import * as assert from "assert"; -import { SparseSet8 } from "../src"; +import { + sparseSet, + SparseSet16, + SparseSet32, + SparseSet8 +} from "../src"; describe("SparseSet", () => { let set: SparseSet8; @@ -10,6 +15,23 @@ describe("SparseSet", () => { set = new SparseSet8(8); }); + it("factory / max value", () => { + let a = sparseSet(0x100); + a.into([0xff, 0x100]); + assert(a instanceof SparseSet8, "u8"); + assert.deepEqual([...a], [0xff]); + + a = sparseSet(0x10000); + a.into([0xffff, 0x10000]); + assert(a instanceof SparseSet16, "u16"); + assert.deepEqual([...a], [0xffff]); + + a = sparseSet(0x10001); + a.into([0x10000, 0x10001]); + assert(a instanceof SparseSet32, "u32"); + assert.deepEqual([...a], [0x10000]); + }); + it("ctor(n)", () => { assert(isSet(set)); assert.equal(set.size, 0); diff --git a/packages/atom/CHANGELOG.md b/packages/atom/CHANGELOG.md index a20a9f285a..ddeb1dd2f4 100644 --- a/packages/atom/CHANGELOG.md +++ b/packages/atom/CHANGELOG.md @@ -3,6 +3,42 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@3.1.0...@thi.ng/atom@3.1.1) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/atom + + + + + +# [3.1.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@3.0.4...@thi.ng/atom@3.1.0) (2019-09-21) + + +### Features + +* **atom:** add Transacted wrapper & tests ([8aaf6e6](https://github.com/thi-ng/umbrella/commit/8aaf6e6)) +* **atom:** update Transacted watch ID handling, update tests, readme ([93d9e1d](https://github.com/thi-ng/umbrella/commit/93d9e1d)) + + + + + +## [3.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@3.0.3...@thi.ng/atom@3.0.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/atom + + + + + +## [3.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@3.0.2...@thi.ng/atom@3.0.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/atom + + + + + ## [3.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@3.0.1...@thi.ng/atom@3.0.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/atom diff --git a/packages/atom/README.md b/packages/atom/README.md index 83687f3739..68ffa12f8f 100644 --- a/packages/atom/README.md +++ b/packages/atom/README.md @@ -7,6 +7,23 @@ This project is part of the [@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + +- [About](#about) + - [Status](#status) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) + - [Atom](#atom) + - [Transacted updates](#transacted-updates) + - [Cursor](#cursor) + - [Derived views](#derived-views) + - [Undo / Redo history](#undo--redo-history) +- [Authors](#authors) +- [License](#license) + + + ## About Clojure inspired mutable wrappers for (usually) immutable values, with @@ -15,44 +32,24 @@ infrastructure support for: - watches - derived view subscriptions - cursors (direct R/W access to nested values) +- transacted updates - undo/redo history Together these types act as building blocks for various application state handling patterns, specifically aimed (though not exclusively) at -the concept of using a nested, immutable, centralized atom as single -source of truth within an application. +the concept of using a centralized atom around a nested, immutable object +as single source of truth within an application. ### Status Stable, used in production and in active development. -**Note: On 2018-03-17 this package was split to remain more focused. -Path based getters/setters have been moved into the new -[@thi.ng/paths](https://github.com/thi-ng/umbrella/tree/master/packages/paths) -package. Likewise, all interceptor based event handling functionality -now lives in the -[@thi.ng/interceptors](https://github.com/thi-ng/umbrella/tree/master/packages/interceptors) -package.** - ## Installation ```bash yarn add @thi.ng/atom ``` -**New since 2018-03-15: You can now create a preconfigured app skeleton -using @thi.ng/atom, @thi.ng/hdom & @thi.ng/router using the -[create-hdom-app](https://github.com/thi-ng/create-hdom-app) project -generator:** - -```bash -yarn create hdom-app my-app - -cd my-app -yarn install -yarn start -``` - ## Dependencies - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) @@ -69,11 +66,11 @@ directory make heavy use of this library. ### Atom -An `Atom` is a mutable wrapper for immutable values. The wrapped value -can be obtained via `deref()`, replaced via `reset()` and updated using -`swap()`. An atom too supports the concept of watches, essentially -`onchange` event handlers which are called from `reset`/`swap` and -receive both the old and new atom values. +An `Atom` is a mutable wrapper for supposedly immutable values. The +wrapped value can be obtained via `deref()`, replaced via `reset()` and +updated using `swap()`. An atom too supports the concept of watches, +essentially `onchange` event handlers which are called from +`reset` / `swap` and receive both the old and new atom values. ```ts import * as atom from "@thi.ng/atom"; @@ -103,6 +100,75 @@ a.reset(42); // foo: 24 -> 42 ``` +When atoms are used to wrap nested object values, the `resetIn()` / +`swapIn()` methods can be used to directly update nested values. These +updates are handled via immutable setters provided by +[@thi.ng/paths](https://github.com/thi-ng/umbrella/tree/master/packages/paths). + +```ts +const db = new Atom({ a: { b: 1, c: 2 } }); + +db.resetIn("a.b", 100); +// { a: { b: 100, c: 2 } } + +db.swapIn("a.c", (x) => x + 1); +// { a: { b: 100, c: 3 } } + +// alternatively, the lookup path can be given as array +// see @thi.ng/paths for further reference +db.swapIn(["a", "c"], (x) => x + 1); +// { a: { b: 100, c: 4 } } +``` + +### Transacted updates + +Since v3.1.0, multiple sequential state updates can be grouped in +transactions and then applied in one go (or canceled altogether). This +can be useful to produce a clean(er) sequence of undo snapshots (see +further below) and avoids multiple / obsolete invocations of watches +caused by each interim state update. Using a transaction, the parent +state is only updated once and watches too are only notified once after +each commit. + +Transactions can also be canceled, thus not impacting the parent state +at all. Nested transactions are *not* supported and attempting to do so +will throw an error. + +The `Transacted` class can wrap any existing `IAtom` implementation, +e.g. `Atom`, `Cursor` or `History` instances and implements `IAtom` +itself... + +```ts +const db = new Atom({ a: 1, b: 2 }); +const tx = new Transacted(db); + +// start transaction +tx.begin(); + +// perform multiple updates +// (none of them are applied until `commit` is called) +// IMPORTANT: calling any of these update methods without +// a running transaction will throw an error! +tx.resetIn("a", 11); +tx.resetIn("c", 33); + +// tx.deref() will always return latest state +tx.deref() +// { a: 11, b: 2, c: 33 } + +// however, at this point db.deref() still yields pre-transaction state +db.deref() +// { a: 1, b: 2 } + +// apply all changes at once (or `cancel()` transaction) +tx.commit(); +// { a: 11, b: 2, c: 33 } + +// verify parent state +db.deref() +// { a: 11, b: 2, c: 33 } +``` + ### Cursor Cursors provide direct & immutable access to a nested value within a @@ -360,15 +426,16 @@ const app = () => start(document.body, app); ``` -### Undo history +### Undo / Redo history The `History` type can be used with & behaves like an Atom or Cursor, but creates snapshots of the current state before applying the new -state. By default history has length of 100 steps, but this is -configurable. +state. By default, the history has length of 100 steps, though this is +configurable via ctor args. ```ts -db = new atom.History(new atom.Atom({a: 1})) +// create history w/ max. 100 steps +db = new atom.History(new atom.Atom({a: 1}), 100) db.deref() // {a: 1} diff --git a/packages/atom/package.json b/packages/atom/package.json index 8f0a3b947e..29d319d612 100644 --- a/packages/atom/package.json +++ b/packages/atom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/atom", - "version": "3.0.2", + "version": "3.1.1", "description": "Mutable wrappers for nested immutable values w/ optional undo/redo history", "module": "./index.js", "main": "./lib/index.js", @@ -29,15 +29,15 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/paths": "^2.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/paths": "^2.1.6" }, "keywords": [ "derived views", @@ -47,8 +47,12 @@ "history", "immutable", "redo", + "state", + "transaction", "typescript", - "undo" + "undo", + "update", + "wrapper" ], "publishConfig": { "access": "public" diff --git a/packages/atom/src/index.ts b/packages/atom/src/index.ts index b5c592cc7e..f3abf70e32 100644 --- a/packages/atom/src/index.ts +++ b/packages/atom/src/index.ts @@ -2,4 +2,5 @@ export * from "./api"; export * from "./atom"; export * from "./cursor"; export * from "./history"; +export * from "./transacted"; export * from "./view"; diff --git a/packages/atom/src/transacted.ts b/packages/atom/src/transacted.ts new file mode 100644 index 0000000000..cf457e76ce --- /dev/null +++ b/packages/atom/src/transacted.ts @@ -0,0 +1,117 @@ +import { assert, Path, Watch } from "@thi.ng/api"; +import { setIn, updateIn } from "@thi.ng/paths"; +import { + IAtom, + IView, + SwapFn, + ViewTransform +} from "./api"; +import { nextID } from "./idgen"; +import { View } from "./view"; + +export class Transacted implements IAtom { + parent: IAtom; + current: T | undefined; + protected id: string; + protected isActive: boolean; + protected _watches: any; + + constructor(parent: IAtom) { + this.parent = parent; + this.current = undefined; + this.isActive = false; + this.id = `tx${nextID()}-`; + } + + get value() { + return this.deref(); + } + + set value(val: T) { + this.reset(val); + } + + get isTransaction() { + return this.isActive; + } + + deref() { + return this.isActive ? this.current! : this.parent.deref(); + } + + equiv(o: any) { + return this === o; + } + + reset(val: T) { + this.ensureTx(); + this.current = val; + return val; + } + + resetIn(path: Path, val: V) { + this.ensureTx(); + return this.reset(setIn(this.current, path, val)); + } + + swap(fn: SwapFn, ...args: any[]) { + this.ensureTx(); + return this.reset(fn.apply(null, [this.current!, ...args])); + } + + swapIn(path: Path, fn: SwapFn, ...args: any[]) { + this.ensureTx(); + return this.reset(updateIn(this.current, path, fn, ...args)); + } + + begin() { + assert(!this.isActive, "transaction already started"); + this.current = this.parent.deref(); + this.isActive = true; + } + + commit() { + this.ensureTx(); + const val = this.current!; + this.parent.reset(this.current!); + this.isActive = false; + this.current = undefined; + return val; + } + + cancel() { + this.ensureTx(); + this.isActive = false; + this.current = undefined; + } + + addWatch(id: string, watch: Watch) { + return this.parent.addWatch(this.id + id, (_, prev, curr) => + watch(id, prev, curr) + ); + } + + removeWatch(id: string) { + return this.parent.removeWatch(this.id + id); + } + + notifyWatches(old: T, curr: T) { + this.parent.notifyWatches(old, curr); + } + + addView(path: Path, tx?: ViewTransform, lazy = true): IView { + return new View(this, path, tx, lazy); + } + + release() { + delete this.parent; + delete this.current; + delete this.isActive; + delete this._watches; + return true; + } + + protected ensureTx() { + assert(this.isActive, "no active transaction"); + } +} diff --git a/packages/atom/test/index.ts b/packages/atom/test/index.ts deleted file mode 100644 index ff27842a95..0000000000 --- a/packages/atom/test/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./atom"; -export * from "./cursor"; -export * from "./history"; -export * from "./view"; diff --git a/packages/atom/test/transacted.ts b/packages/atom/test/transacted.ts new file mode 100644 index 0000000000..ab3c6bfbbb --- /dev/null +++ b/packages/atom/test/transacted.ts @@ -0,0 +1,87 @@ +import * as assert from "assert"; +import { Atom, Transacted } from "../src/index"; + +describe("transacted", () => { + let db: Atom; + let tx: Transacted; + + beforeEach(() => { + db = new Atom({ a: 1, b: 2 }); + tx = new Transacted(db); + }); + + it("initial", () => { + assert.deepEqual(db.deref(), { a: 1, b: 2 }); + assert.deepStrictEqual(tx.deref(), db.deref()); + }); + + it("transaction", () => { + tx.begin(); + assert.deepEqual(tx.deref(), { a: 1, b: 2 }); + assert.throws(() => tx.begin(), "no nested tx"); + tx.swapIn("a", (x: number) => x + 10); + tx.swapIn("b", (x: number) => x + 20); + assert.deepEqual(tx.deref(), { a: 11, b: 22 }); + assert.deepEqual(db.deref(), { a: 1, b: 2 }); + assert.deepEqual(tx.commit(), { a: 11, b: 22 }); + assert.deepEqual(tx.deref(), { a: 11, b: 22 }); + assert.deepStrictEqual(tx.deref(), db.deref()); + assert.throws(() => tx.commit(), "no double commit"); + }); + + it("cancel", () => { + tx.begin(); + tx.swapIn("a", (x: number) => x + 10); + assert.deepEqual(tx.deref(), { a: 11, b: 2 }); + tx.cancel(); + assert.deepEqual(tx.deref(), { a: 1, b: 2 }); + assert.deepStrictEqual(tx.deref(), db.deref()); + assert.throws(() => tx.cancel(), "no double cancel"); + }); + + it("no edits outside tx", () => { + assert.throws(() => tx.reset({}), "no reset"); + assert.throws(() => tx.swap(() => ({})), "no swap"); + assert.throws(() => tx.resetIn("a", {}), "no resetIn"); + assert.throws(() => tx.swapIn("a", () => ({})), "no swapIn"); + assert.throws(() => (tx.value = {}), "no .value"); + }); + + it("watches", () => { + let count = 0; + tx.addWatch("foo", (id, old, curr) => { + count++; + assert.equal(id, "foo"); + assert.deepEqual(old, { a: 1, b: 2 }); + assert.deepEqual(curr, { a: 22 }); + }); + tx.begin(); + tx.reset({ a: 11 }); + tx.reset({ a: 22 }); + tx.commit(); + assert.equal(count, 1); + }); + + it("view (lazy)", () => { + const acc: any[] = []; + const view = tx.addView("a", (x) => (acc.push(x), x), true); + assert.equal(view.deref(), 1); + tx.begin(); + tx.reset({ a: 11 }); + tx.reset({ a: 22 }); + tx.commit(); + assert.equal(view.deref(), 22); + assert.deepEqual(acc, [1, 22]); + }); + + it("view (eager)", () => { + const acc: any[] = []; + const view = tx.addView("a", (x) => (acc.push(x), x), false); + tx.begin(); + tx.reset({ a: 11 }); + tx.reset({ a: 22 }); + tx.commit(); + assert.deepEqual(acc, [1, 22]); + assert.equal(view.deref(), 22); + }); +}); diff --git a/packages/bench/CHANGELOG.md b/packages/bench/CHANGELOG.md index 25babc32a6..40d934660e 100644 --- a/packages/bench/CHANGELOG.md +++ b/packages/bench/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/bench@1.0.9...@thi.ng/bench@1.0.10) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/bench + + + + + ## [1.0.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/bench@1.0.8...@thi.ng/bench@1.0.9) (2019-07-31) **Note:** Version bump only for package @thi.ng/bench diff --git a/packages/bench/package.json b/packages/bench/package.json index 2f2226c960..a0adf75d4a 100644 --- a/packages/bench/package.json +++ b/packages/bench/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/bench", - "version": "1.0.9", + "version": "1.0.10", "description": "Basic benchmarking helpers", "module": "./index.js", "main": "./lib/index.js", @@ -29,8 +29,8 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "keywords": [ "benchmark", diff --git a/packages/bencode/CHANGELOG.md b/packages/bencode/CHANGELOG.md index b9d53d5d0b..1c8a820518 100644 --- a/packages/bencode/CHANGELOG.md +++ b/packages/bencode/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/bencode@0.3.5...@thi.ng/bencode@0.3.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/bencode + + + + + +## [0.3.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/bencode@0.3.4...@thi.ng/bencode@0.3.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/bencode + + + + + +## [0.3.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/bencode@0.3.3...@thi.ng/bencode@0.3.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/bencode + + + + + +## [0.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/bencode@0.3.2...@thi.ng/bencode@0.3.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/bencode + + + + + ## [0.3.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/bencode@0.3.1...@thi.ng/bencode@0.3.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/bencode diff --git a/packages/bencode/package.json b/packages/bencode/package.json index 04d4a983ef..bd77f44df4 100644 --- a/packages/bencode/package.json +++ b/packages/bencode/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/bencode", - "version": "0.3.2", + "version": "0.3.6", "description": "Bencode binary encoder / decoder with optional UTF8 encoding", "module": "./index.js", "main": "./lib/index.js", @@ -29,17 +29,17 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/arrays": "^0.2.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/defmulti": "^1.1.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/transducers-binary": "^0.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/arrays": "^0.3.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/defmulti": "^1.2.0", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/transducers-binary": "^0.4.6" }, "keywords": [ "bencode", diff --git a/packages/binary/CHANGELOG.md b/packages/binary/CHANGELOG.md index 6fd00929b5..e144b8ee92 100644 --- a/packages/binary/CHANGELOG.md +++ b/packages/binary/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/binary@1.1.0...@thi.ng/binary@1.1.1) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/binary + + + + + # [1.1.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/binary@1.0.8...@thi.ng/binary@1.1.0) (2019-07-31) diff --git a/packages/binary/package.json b/packages/binary/package.json index b997393788..7a688ced4a 100644 --- a/packages/binary/package.json +++ b/packages/binary/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/binary", - "version": "1.1.0", + "version": "1.1.1", "description": "Assorted binary / bitwise operations, conversions, utilities.", "module": "./index.js", "main": "./lib/index.js", @@ -29,8 +29,8 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "keywords": [ "alignment", diff --git a/packages/binary/src/pow.ts b/packages/binary/src/pow.ts index a9a5a1d6be..f2ebf1c0cb 100644 --- a/packages/binary/src/pow.ts +++ b/packages/binary/src/pow.ts @@ -1,6 +1,8 @@ +import { Pow2 } from "./api"; + // http://graphics.stanford.edu/~seander/bithacks.html -export const isPow2 = (x: number) => !!x && !(x & (x - 1)); +export const isPow2 = (x: number): x is Pow2 => !!x && !(x & (x - 1)); export const ceilPow2 = (x: number) => { x += (x === 0); diff --git a/packages/bitfield/CHANGELOG.md b/packages/bitfield/CHANGELOG.md index 1f59075fda..b04c01d6db 100644 --- a/packages/bitfield/CHANGELOG.md +++ b/packages/bitfield/CHANGELOG.md @@ -3,6 +3,33 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitfield@0.2.0...@thi.ng/bitfield@0.2.1) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/bitfield + + + + + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitfield@0.1.12...@thi.ng/bitfield@0.2.0) (2019-09-21) + + +### Features + +* **bitfield:** update BitMatrix to support non-squared sizes, update docstrings ([0fd8620](https://github.com/thi-ng/umbrella/commit/0fd8620)) + + + + + +## [0.1.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitfield@0.1.11...@thi.ng/bitfield@0.1.12) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/bitfield + + + + + ## [0.1.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitfield@0.1.10...@thi.ng/bitfield@0.1.11) (2019-07-31) **Note:** Version bump only for package @thi.ng/bitfield diff --git a/packages/bitfield/README.md b/packages/bitfield/README.md index 5b01602adc..d1ad9f809b 100644 --- a/packages/bitfield/README.md +++ b/packages/bitfield/README.md @@ -20,7 +20,8 @@ This project is part of the ## About -Typed array backed 1D / 2D bit field implementations. +Typed array backed 1D / 2D bit field / bit matrix implementations. Due +to `Uint32Array` backing, width is always a multiple of 32. ## Installation @@ -37,6 +38,45 @@ yarn add @thi.ng/bitfield ```ts import { BitField, BitMatrix } from "@thi.ng/bitfield"; + +// size always rounded up to a multiple of 32 +const field = new BitField(16); + +field.setAt(0); + +// if 2nd arg is false, the bit will be cleared +// setAt returns non-zero value if bit was previously set +field.setAt(31, true); +// 0 + +// returns non-zero value if bit is set +field.at(0) +// -2147483648 + +field.at(1) +// 0 + +field.toString(); +// 10000000000000000000000000000001 + +field.resize(64) +// 1000000000000000000000000000000100000000000000000000000000000000 + +const mat = new BitMatrix(8, 32); +for(let i = 0; i < 8; i++) mat.setAt(i, i); + +mat.at(7, 7); +// 16777216 + +mat.toString(); +// 10000000000000000000000000000000 +// 01000000000000000000000000000000 +// 00100000000000000000000000000000 +// 00010000000000000000000000000000 +// 00001000000000000000000000000000 +// 00000100000000000000000000000000 +// 00000010000000000000000000000000 +// 00000001000000000000000000000000 ``` ## Authors diff --git a/packages/bitfield/package.json b/packages/bitfield/package.json index 9a11b37e52..1d5a30d90b 100644 --- a/packages/bitfield/package.json +++ b/packages/bitfield/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/bitfield", - "version": "0.1.11", + "version": "0.2.1", "description": "1D / 2D bit field implementations", "module": "./index.js", "main": "./lib/index.js", @@ -29,13 +29,13 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/binary": "^1.1.0", - "@thi.ng/strings": "^1.2.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/binary": "^1.1.1", + "@thi.ng/strings": "^1.3.1" }, "keywords": [ "1D", diff --git a/packages/bitfield/src/bitfield.ts b/packages/bitfield/src/bitfield.ts index f6d7853c3b..676edeb0e0 100644 --- a/packages/bitfield/src/bitfield.ts +++ b/packages/bitfield/src/bitfield.ts @@ -1,8 +1,10 @@ import { align } from "@thi.ng/binary"; -import { radix } from "@thi.ng/strings"; - -const B32 = radix(2, 32); +import { B32 } from "@thi.ng/strings"; +/** + * 1D bit field, backed by a Uint32Array. Size is always a multiple of + * 32. + */ export class BitField { data: Uint32Array; n: number; @@ -13,7 +15,8 @@ export class BitField { } /** - * Resizes bitfield to new size given (aligned to multiples of 32). + * Resizes bitfield to new size given (rounded up to multiples of + * 32). * * @param n */ @@ -27,7 +30,8 @@ export class BitField { } /** - * Returns a non-zero value if bit `n` is enabled. + * Returns a non-zero value if bit `n` is enabled. No bounds + * checking. * * @param n */ @@ -37,7 +41,7 @@ export class BitField { /** * Enables or disables bit `n`. Returns a non-zero value if the bit - * was previously enabled. + * was previously enabled. No bounds checking. * . * @param n * @param v diff --git a/packages/bitfield/src/bitmatrix.ts b/packages/bitfield/src/bitmatrix.ts index 87a47be78b..6805f27110 100644 --- a/packages/bitfield/src/bitmatrix.ts +++ b/packages/bitfield/src/bitmatrix.ts @@ -1,38 +1,45 @@ import { align } from "@thi.ng/binary"; -import { radix } from "@thi.ng/strings"; - -const B32 = radix(2, 32); +import { B32 } from "@thi.ng/strings"; +/** + * MxN row-major 2D bit matrix, backed by a Uint32Array. Width is always + * a multiple of 32. + */ export class BitMatrix { data: Uint32Array; stride: number; + m: number; n: number; - constructor(n: number) { + constructor(m: number, n = m) { + this.m = m; this.n = n = align(n, 32); this.stride = n >>> 5; - this.data = new Uint32Array(n * this.stride); + this.data = new Uint32Array(m * this.stride); } /** - * Resizes matrix to new size given (aligned to multiples of 32). + * Resizes matrix to new size given (width always rounded up to + * multiples of 32). * - * @param n + * @param m new number of rows + * @param n new number of cols */ - resize(n: number) { + resize(m: number, n = m) { n = align(n, 32); const dstride = n >>> 5; const sstride = this.stride; const w = Math.min(dstride, sstride); const src = this.data; - const dest = new Uint32Array(n * dstride); + const dest = new Uint32Array(m * dstride); for ( - let i = this.n - 1, si = i * sstride, di = i * dstride; + let i = Math.min(m, this.m) - 1, si = i * sstride, di = i * dstride; i >= 0; i--, si -= sstride, di -= dstride ) { dest.set(src.slice(si, si + w), di); } + this.m = m; this.n = n; this.stride = dstride; this.data = dest; @@ -41,6 +48,7 @@ export class BitMatrix { /** * Returns a non-zero value if bit at `m,n` is enabled (row major). + * No bounds checking. * * @param m * @param n @@ -53,7 +61,7 @@ export class BitMatrix { /** * Enables or disables bit at `m,n` (row major). Returns a non-zero - * value if the bit was previously enabled. + * value if the bit was previously enabled. No bounds checking. * . * @param m * @param n @@ -73,7 +81,7 @@ export class BitMatrix { toString() { const res: string[] = []; - for (let i = 0, j = 0, s = this.stride; i < this.n; i++, j += s) { + for (let i = 0, j = 0, s = this.stride; i < this.m; i++, j += s) { res.push([...this.data.slice(j, j + s)].map(B32).join("")); } return res.join("\n"); diff --git a/packages/bitstream/CHANGELOG.md b/packages/bitstream/CHANGELOG.md index 2bda5d565e..69219d1316 100644 --- a/packages/bitstream/CHANGELOG.md +++ b/packages/bitstream/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitstream@1.1.3...@thi.ng/bitstream@1.1.4) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/bitstream + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitstream@1.1.2...@thi.ng/bitstream@1.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/bitstream + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitstream@1.1.1...@thi.ng/bitstream@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/bitstream diff --git a/packages/bitstream/package.json b/packages/bitstream/package.json index 6ba9a47329..a04fd302d7 100644 --- a/packages/bitstream/package.json +++ b/packages/bitstream/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/bitstream", - "version": "1.1.2", + "version": "1.1.4", "description": "ES6 iterator based read/write bit streams & support for variable word widths", "module": "./index.js", "main": "./lib/index.js", @@ -25,15 +25,15 @@ "pub": "yarn build:release && yarn publish --access public" }, "dependencies": { - "@thi.ng/errors": "^1.1.2" + "@thi.ng/errors": "^1.2.1" }, "devDependencies": { "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "keywords": [ "binary", diff --git a/packages/cache/CHANGELOG.md b/packages/cache/CHANGELOG.md index 0899538a90..af4293a521 100644 --- a/packages/cache/CHANGELOG.md +++ b/packages/cache/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@1.0.25...@thi.ng/cache@1.0.26) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/cache + + + + + +## [1.0.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@1.0.24...@thi.ng/cache@1.0.25) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/cache + + + + + +## [1.0.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@1.0.23...@thi.ng/cache@1.0.24) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/cache + + + + + +## [1.0.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@1.0.22...@thi.ng/cache@1.0.23) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/cache + + + + + ## [1.0.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@1.0.21...@thi.ng/cache@1.0.22) (2019-07-31) **Note:** Version bump only for package @thi.ng/cache diff --git a/packages/cache/package.json b/packages/cache/package.json index 40e62f3263..37e7288b87 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/cache", - "version": "1.0.22", + "version": "1.0.26", "description": "In-memory cache implementations with ES6 Map-like API and different eviction strategies", "module": "./index.js", "main": "./lib/index.js", @@ -29,13 +29,13 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/dcons": "^2.1.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/dcons": "^2.1.6", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "cache", diff --git a/packages/cache/src/lru.ts b/packages/cache/src/lru.ts index 1c42b537b6..1d1c479894 100644 --- a/packages/cache/src/lru.ts +++ b/packages/cache/src/lru.ts @@ -100,24 +100,8 @@ export class LRUCache implements ICache { set(key: K, value: V) { const size = this.opts.ksize(key) + this.opts.vsize(value); const e = this.map.get(key); - if (e) { - this._size -= e.value.s; - } - this._size += size; - if (this.ensureSize()) { - if (e) { - e.value.v = value; - e.value.s = size; - this.items.asTail(e); - } else { - this.items.push({ - k: key, - v: value, - s: size - }); - this.map.set(key, this.items.tail!); - } - } + this._size += Math.max(0, size - (e ? e.value.s : 0)); + this.ensureSize() && this.doSetEntry(e, key, value, size); return value; } @@ -173,4 +157,20 @@ export class LRUCache implements ICache { this.opts.release && this.opts.release(ee.k, ee.v); this._size -= ee.s; } + + protected doSetEntry( + e: ConsCell> | undefined, + k: K, + v: V, + s: number + ) { + if (e) { + e.value.v = v; + e.value.s = s; + this.items.asTail(e); + } else { + this.items.push({ k, v, s }); + this.map.set(k, this.items.tail!); + } + } } diff --git a/packages/cache/src/mru.ts b/packages/cache/src/mru.ts index e00213cb33..47800530f3 100644 --- a/packages/cache/src/mru.ts +++ b/packages/cache/src/mru.ts @@ -14,32 +14,24 @@ export class MRUCache extends LRUCache { return new MRUCache(null, this.opts); } - set(key: K, value: V) { - const size = this.opts.ksize(key) + this.opts.vsize(value); - const e = this.map.get(key); - if (e) { - this._size -= e.value.s; - } - this._size += size; - if (this.ensureSize()) { - if (e) { - e.value.v = value; - e.value.s = size; - this.items.asHead(e); - } else { - this.items.cons({ - k: key, - v: value, - s: size - }); - this.map.set(key, this.items.head!); - } - } - return value; - } - protected resetEntry(e: ConsCell>) { this.items.asHead(e); return e.value.v; } + + protected doSetEntry( + e: ConsCell> | undefined, + k: K, + v: V, + s: number + ) { + if (e) { + e.value.v = v; + e.value.s = s; + this.items.asHead(e); + } else { + this.items.cons({ k, v, s }); + this.map.set(k, this.items.head!); + } + } } diff --git a/packages/cache/src/tlru.ts b/packages/cache/src/tlru.ts index e21940b3ee..99794b6b67 100644 --- a/packages/cache/src/tlru.ts +++ b/packages/cache/src/tlru.ts @@ -56,10 +56,7 @@ export class TLRUCache extends LRUCache { set(key: K, value: V, ttl = this.opts.ttl) { const size = this.opts.ksize(key) + this.opts.vsize(value); const e = this.map.get(key); - if (e) { - this._size -= e.value.s; - } - this._size += size; + this._size += Math.max(0, size - (e ? e.value.s : 0)); if (this.ensureSize()) { const t = Date.now() + ttl; if (e) { diff --git a/packages/checks/CHANGELOG.md b/packages/checks/CHANGELOG.md index c0cd2c4495..34a36a63d3 100644 --- a/packages/checks/CHANGELOG.md +++ b/packages/checks/CHANGELOG.md @@ -3,6 +3,43 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.4.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/checks@2.4.0...@thi.ng/checks@2.4.1) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/checks + + + + + +# [2.4.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/checks@2.3.0...@thi.ng/checks@2.4.0) (2019-09-21) + + +### Features + +* **checks:** add generics to existsAndNotNull() ([bced8b9](https://github.com/thi-ng/umbrella/commit/bced8b9)) + + + + + +# [2.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/checks@2.2.2...@thi.ng/checks@2.3.0) (2019-08-16) + + +### Bug Fixes + +* **checks:** better hex string, export, isNil doc ([19b1981](https://github.com/thi-ng/umbrella/commit/19b1981)) +* **checks:** fix vscode autoimport ([8ac6408](https://github.com/thi-ng/umbrella/commit/8ac6408)) +* **checks:** test, better naming ([90dce20](https://github.com/thi-ng/umbrella/commit/90dce20)) + + +### Features + +* **checks:** isNil and isHexColorString ([ebaa15e](https://github.com/thi-ng/umbrella/commit/ebaa15e)) + + + + + ## [2.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/checks@2.2.1...@thi.ng/checks@2.2.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/checks diff --git a/packages/checks/package.json b/packages/checks/package.json index 96f9a0e861..2a09a226fd 100644 --- a/packages/checks/package.json +++ b/packages/checks/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/checks", - "version": "2.2.2", + "version": "2.4.1", "description": "Single-function sub-modules for type, feature & value checks", "module": "./index.js", "main": "./lib/index.js", @@ -29,8 +29,8 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "keywords": [ "check", diff --git a/packages/checks/src/exists-not-null.ts b/packages/checks/src/exists-not-null.ts index c29fa61e82..d1a3264940 100644 --- a/packages/checks/src/exists-not-null.ts +++ b/packages/checks/src/exists-not-null.ts @@ -1 +1,2 @@ -export const existsAndNotNull = (x: any) => x != null; +export const existsAndNotNull = (x: T | null | undefined): x is T => + x != null; diff --git a/packages/checks/src/exists.ts b/packages/checks/src/exists.ts index 305311d128..bf3155129d 100644 --- a/packages/checks/src/exists.ts +++ b/packages/checks/src/exists.ts @@ -1 +1 @@ -export const exists = (x: any) => x !== undefined; +export const exists = (t: T | undefined): t is T => t !== undefined; diff --git a/packages/checks/src/index.ts b/packages/checks/src/index.ts index d952c8af1f..4e6a2573ee 100644 --- a/packages/checks/src/index.ts +++ b/packages/checks/src/index.ts @@ -19,6 +19,7 @@ export * from "./is-false"; export * from "./is-file"; export * from "./is-firefox"; export * from "./is-function"; +export * from "./is-hex-color"; export * from "./is-ie"; export * from "./is-in-range"; export * from "./is-int32"; @@ -27,6 +28,7 @@ export * from "./is-map"; export * from "./is-mobile"; export * from "./is-nan"; export * from "./is-negative"; +export * from "./is-nil"; export * from "./is-node"; export * from "./is-not-string-iterable"; export * from "./is-null"; diff --git a/packages/checks/src/is-hex-color.ts b/packages/checks/src/is-hex-color.ts new file mode 100644 index 0000000000..804a845ba7 --- /dev/null +++ b/packages/checks/src/is-hex-color.ts @@ -0,0 +1,5 @@ +import { isString } from "./is-string"; + +const RE = /^#([a-f0-9]{3}|[a-f0-9]{4}(?:[a-f0-9]{2}){0,2})$/i; + +export const isHexColor = (x: any): x is string => isString(x) && RE.test(x); diff --git a/packages/checks/src/is-nil.ts b/packages/checks/src/is-nil.ts new file mode 100644 index 0000000000..5aee3135c9 --- /dev/null +++ b/packages/checks/src/is-nil.ts @@ -0,0 +1,5 @@ +/** + * Checks if x is null or undefined. + * + */ +export const isNil = (x: any): x is null | undefined => x == null; diff --git a/packages/checks/test/index.ts b/packages/checks/test/index.ts index bab4eb88d9..56af44064a 100644 --- a/packages/checks/test/index.ts +++ b/packages/checks/test/index.ts @@ -11,6 +11,8 @@ import { isString } from "../src/is-string"; import { isSymbol } from "../src/is-symbol"; import { isTransferable } from "../src/is-transferable"; import { isTypedArray } from "../src/is-typedarray"; +import { isNil } from "../src/is-nil"; +import { isHexColor } from "../src/is-hex-color"; describe("checks", function() { it("existsAndNotNull", () => { @@ -152,4 +154,37 @@ describe("checks", function() { assert.ok(!isTransferable(null), "null"); assert.ok(!isTransferable(undefined), "undefined"); }); + + it("isNil", () => { + assert.ok(isNil(undefined), "undefined"); + assert.ok(isNil(null), "null"); + assert.ok(!isNil("foo"), "string"); + assert.ok(!isNil({}), "empty object"); + assert.ok(!isNil([]), "empty array"); + assert.ok(!isNil(""), "empty string"); + assert.ok(!isNil(false), "false"); + assert.ok(!isNil(true), "true"); + assert.ok(!isNil(() => {}), "function"); + }); + + it("isHexColor", () => { + assert.ok(isHexColor("#123"), "valid 3 digits rgb"); + assert.ok(isHexColor("#ff3300"), "valid 6 digits rrggbb"); + assert.ok(isHexColor("#f30f"), "valid 4 digits rgba"); + assert.ok(isHexColor("#ff3300ff"), "valid 8 digits rrggbbaa"); + assert.ok(!isHexColor(undefined), "undefined"); + assert.ok(!isHexColor(null), "null"); + assert.ok(!isHexColor(""), "empty string"); + assert.ok(!isHexColor("foo"), "invalid: foo"); + assert.ok(!isHexColor("123"), "invalid: 123"); + assert.ok(!isHexColor("#12."), "invalid: #12."); + assert.ok(!isHexColor("#j23"), "invalid: #j23"); + assert.ok(!isHexColor("#jf3300"), "invalid: #jf3300"); + assert.ok(!isHexColor("#j30f"), "invalid: #j30f"); + assert.ok(!isHexColor("#jf3300ff"), "invalid: #jf3300ff"); + assert.ok(!isHexColor("hi #123"), "invalid: hi #123"); + assert.ok(!isHexColor("#ff3300 hi"), "invalid: #ff3300 hi"); + assert.ok(!isHexColor("hi #ff3300 hi"), "invalid: hi #ff3300 hi"); + assert.ok(!isHexColor("#123 #123"), "invalid: #123 #123"); + }); }); diff --git a/packages/color/CHANGELOG.md b/packages/color/CHANGELOG.md index bb2e3066e7..843fddff78 100644 --- a/packages/color/CHANGELOG.md +++ b/packages/color/CHANGELOG.md @@ -3,6 +3,63 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/color@1.1.1...@thi.ng/color@1.1.2) (2019-11-09) + + +### Bug Fixes + +* **color:** update/rename imports (vectors pkg) ([7cb8877](https://github.com/thi-ng/umbrella/commit/7cb88771f88fc329a2728d9f86a18faf04ab0c35)) + + + + + +## [1.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/color@1.1.0...@thi.ng/color@1.1.1) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/color + + + + + +# [1.1.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/color@1.0.3...@thi.ng/color@1.1.0) (2019-08-21) + + +### Features + +* **color:** add resolveAsCSS(), update deps ([f96ac92](https://github.com/thi-ng/umbrella/commit/f96ac92)) + + + + + +## [1.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/color@1.0.2...@thi.ng/color@1.0.3) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/color + + + + + +## [1.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/color@1.0.1...@thi.ng/color@1.0.2) (2019-08-16) + + +### Bug Fixes + +* **color:** add proper rounding to rgbaInt() ([d956954](https://github.com/thi-ng/umbrella/commit/d956954)) + + + + + +## [1.0.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/color@1.0.0...@thi.ng/color@1.0.1) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/color + + + + + # [1.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/color@0.2.2...@thi.ng/color@1.0.0) (2019-07-31) diff --git a/packages/color/package.json b/packages/color/package.json index 4a336fc0f8..52f245e9ba 100644 --- a/packages/color/package.json +++ b/packages/color/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/color", - "version": "1.0.0", + "version": "1.1.2", "description": "Raw, array-based, color ops, conversions, opt. type wrappers, multi-color gradients", "module": "./index.js", "main": "./lib/index.js", @@ -29,18 +29,19 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/compose": "^1.3.2", - "@thi.ng/defmulti": "^1.1.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/math": "^1.4.2", - "@thi.ng/strings": "^1.2.2", - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/compose": "^1.3.5", + "@thi.ng/defmulti": "^1.2.0", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/math": "^1.5.0", + "@thi.ng/strings": "^1.3.1", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "alpha", @@ -75,5 +76,8 @@ "process": false, "setTimeout": false }, - "sideEffects": false + "sideEffects": false, + "thi.ng": { + "shortlink": false + } } diff --git a/packages/color/src/cosine-gradients.ts b/packages/color/src/cosine-gradients.ts index 69cc148018..4412f68aa5 100644 --- a/packages/color/src/cosine-gradients.ts +++ b/packages/color/src/cosine-gradients.ts @@ -2,11 +2,11 @@ import { IObjectOf } from "@thi.ng/api"; import { partial } from "@thi.ng/compose"; import { clamp01, TAU } from "@thi.ng/math"; import { - interpolate, map, normRange, push, transduce, + tween, zip } from "@thi.ng/transducers"; import { Color, CosGradientSpec, ReadonlyColor } from "./api"; @@ -183,12 +183,21 @@ export const cosineCoeffs = (from: ReadonlyColor, to: ReadonlyColor) => { * ) * ``` * - * @see thi.ng/transducers/iter/interpolate + * @see thi.ng/transducers/iter/tween * - * @param n + * @param num * @param stops */ export const multiCosineGradient = ( - n: number, + num: number, ...stops: [number, ReadonlyColor][] -): Color[] => [...interpolate(n, 0, 1, cosineCoeffs, cosineColor, ...stops)]; +): Color[] => [ + ...tween({ + num, + min: 0, + max: 1, + init: cosineCoeffs, + mix: cosineColor, + stops + }) +]; diff --git a/packages/color/src/index.ts b/packages/color/src/index.ts index ad0a393a15..5c9e6aacd9 100644 --- a/packages/color/src/index.ts +++ b/packages/color/src/index.ts @@ -13,6 +13,7 @@ export * from "./hue-rgba"; export * from "./int-css"; export * from "./int-rgba"; export * from "./kelvin-rgba"; +export * from "./resolve"; export * from "./rgba-css"; export * from "./rgba-hcva"; export * from "./rgba-hcya"; diff --git a/packages/color/src/internal/acolor.ts b/packages/color/src/internal/acolor.ts index 00de8a7340..de4d7b3ecf 100644 --- a/packages/color/src/internal/acolor.ts +++ b/packages/color/src/internal/acolor.ts @@ -1,6 +1,6 @@ import { IDeref } from "@thi.ng/api"; import { EPS } from "@thi.ng/math"; -import { eqDelta4, values } from "@thi.ng/vectors"; +import { eqDelta4, stridedValues } from "@thi.ng/vectors"; import { Color, ColorMode, IColor } from "../api"; export abstract class AColor implements IColor, IDeref { @@ -16,7 +16,7 @@ export abstract class AColor implements IColor, IDeref { } [Symbol.iterator]() { - return values(this.buf, 4, this.offset, this.stride); + return stridedValues(this.buf, 4, this.offset, this.stride); } abstract get mode(): ColorMode; diff --git a/packages/color/src/resolve.ts b/packages/color/src/resolve.ts new file mode 100644 index 0000000000..80fa1b4c7c --- /dev/null +++ b/packages/color/src/resolve.ts @@ -0,0 +1,23 @@ +import { isArrayLike, isNumber } from "@thi.ng/checks"; +import { ColorMode, ReadonlyColor } from "./api"; +import { asCSS } from "./convert"; + +/** + * Takes a color in one of the following formats and tries to convert it + * to a CSS string: + * + * - any IColor instance + * - raw RGBA vector + * - number ((A)RGB int) + * - string (unchanged) + * + * @param col + */ +export const resolveAsCSS = (col: any) => + isArrayLike(col) + ? isNumber((col).mode) + ? asCSS(col) + : asCSS(col, ColorMode.RGBA) + : isNumber(col) + ? asCSS(col, ColorMode.INT32) + : col; diff --git a/packages/color/src/rgba-int.ts b/packages/color/src/rgba-int.ts index e4a4387a22..a559ca739f 100644 --- a/packages/color/src/rgba-int.ts +++ b/packages/color/src/rgba-int.ts @@ -3,8 +3,8 @@ import { ReadonlyColor } from "./api"; import { ensureAlpha } from "./internal/ensure-alpha"; export const rgbaInt = (src: ReadonlyColor) => - (((ensureAlpha(src[3]) * 0xff) << 24) | - ((clamp01(src[0]) * 0xff) << 16) | - ((clamp01(src[1]) * 0xff) << 8) | - (clamp01(src[2]) * 0xff)) >>> + (((ensureAlpha(src[3]) * 0xff + 0.5) << 24) | + ((clamp01(src[0]) * 0xff + 0.5) << 16) | + ((clamp01(src[1]) * 0xff + 0.5) << 8) | + (clamp01(src[2]) * 0xff + 0.5)) >>> 0; diff --git a/packages/compare/CHANGELOG.md b/packages/compare/CHANGELOG.md index 8127c5cce0..55ac7a34ba 100644 --- a/packages/compare/CHANGELOG.md +++ b/packages/compare/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/compare@1.0.9...@thi.ng/compare@1.0.10) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/compare + + + + + ## [1.0.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/compare@1.0.8...@thi.ng/compare@1.0.9) (2019-07-31) **Note:** Version bump only for package @thi.ng/compare diff --git a/packages/compare/package.json b/packages/compare/package.json index 3edee2c7b9..921b9a10ae 100644 --- a/packages/compare/package.json +++ b/packages/compare/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/compare", - "version": "1.0.9", + "version": "1.0.10", "description": "Comparator with optional delegation for types implementing @thi.ng/api/ICompare interface", "module": "./index.js", "main": "./lib/index.js", @@ -29,8 +29,8 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "keywords": [ "comparator", diff --git a/packages/compose/CHANGELOG.md b/packages/compose/CHANGELOG.md index 6fd888eee3..9905eef4f7 100644 --- a/packages/compose/CHANGELOG.md +++ b/packages/compose/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.3.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/compose@1.3.4...@thi.ng/compose@1.3.5) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/compose + + + + + +## [1.3.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/compose@1.3.3...@thi.ng/compose@1.3.4) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/compose + + + + + +## [1.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/compose@1.3.2...@thi.ng/compose@1.3.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/compose + + + + + ## [1.3.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/compose@1.3.1...@thi.ng/compose@1.3.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/compose diff --git a/packages/compose/package.json b/packages/compose/package.json index 63cf3c8af4..912ef70178 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/compose", - "version": "1.3.2", + "version": "1.3.5", "description": "Arity-optimized functional composition helpers", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/errors": "^1.2.1" }, "keywords": [ "composition", diff --git a/packages/csp/CHANGELOG.md b/packages/csp/CHANGELOG.md index df883bc514..d4bd8f4048 100644 --- a/packages/csp/CHANGELOG.md +++ b/packages/csp/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@1.1.5...@thi.ng/csp@1.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/csp + + + + + +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@1.1.4...@thi.ng/csp@1.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/csp + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@1.1.3...@thi.ng/csp@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/csp + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@1.1.2...@thi.ng/csp@1.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/csp + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@1.1.1...@thi.ng/csp@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/csp diff --git a/packages/csp/package.json b/packages/csp/package.json index 4946e98f4b..98835f815a 100644 --- a/packages/csp/package.json +++ b/packages/csp/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/csp", - "version": "1.1.2", + "version": "1.1.6", "description": "ES6 promise based CSP implementation", "module": "./index.js", "main": "./lib/index.js", @@ -33,16 +33,16 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/arrays": "^0.2.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/dcons": "^2.1.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/arrays": "^0.3.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/dcons": "^2.1.6", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "async", diff --git a/packages/dcons/CHANGELOG.md b/packages/dcons/CHANGELOG.md index 93eb3e819e..66c23ff2c3 100644 --- a/packages/dcons/CHANGELOG.md +++ b/packages/dcons/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@2.1.5...@thi.ng/dcons@2.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/dcons + + + + + +## [2.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@2.1.4...@thi.ng/dcons@2.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/dcons + + + + + +## [2.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@2.1.3...@thi.ng/dcons@2.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/dcons + + + + + +## [2.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@2.1.2...@thi.ng/dcons@2.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/dcons + + + + + ## [2.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@2.1.1...@thi.ng/dcons@2.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/dcons diff --git a/packages/dcons/package.json b/packages/dcons/package.json index 38ab47e5e4..792be03adb 100644 --- a/packages/dcons/package.json +++ b/packages/dcons/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dcons", - "version": "2.1.2", + "version": "2.1.6", "description": "Comprehensive doubly linked list structure w/ iterator support", "module": "./index.js", "main": "./lib/index.js", @@ -29,16 +29,16 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/compare": "^1.0.9", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/compare": "^1.0.10", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "datastructure", diff --git a/packages/defmulti/CHANGELOG.md b/packages/defmulti/CHANGELOG.md index e73de684cd..33224cdb76 100644 --- a/packages/defmulti/CHANGELOG.md +++ b/packages/defmulti/CHANGELOG.md @@ -3,6 +3,33 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/defmulti@1.1.4...@thi.ng/defmulti@1.2.0) (2019-11-09) + + +### Features + +* **defmulti:** allow .add() to overwrite existing impl, add logger ([e387622](https://github.com/thi-ng/umbrella/commit/e387622d3ad44bc0df029c5ba641244dc12c6353)) + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/defmulti@1.1.3...@thi.ng/defmulti@1.1.4) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/defmulti + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/defmulti@1.1.2...@thi.ng/defmulti@1.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/defmulti + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/defmulti@1.1.1...@thi.ng/defmulti@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/defmulti diff --git a/packages/defmulti/README.md b/packages/defmulti/README.md index e1ec641b64..478d9ad2d3 100644 --- a/packages/defmulti/README.md +++ b/packages/defmulti/README.md @@ -46,7 +46,8 @@ varargs solution. The function returned by `defmulti` can be called like any other function, but also exposes the following operations: -- `.add(id, fn)` - adds new implementation for given dispatch value +- `.add(id, fn)` - adds/overrides implementation for given dispatch + value - `.remove(id)` - removes implementation for dispatch value - `.callable(...args)` - takes same args as if calling the multi-function, but only checks if an implementation exists for the diff --git a/packages/defmulti/package.json b/packages/defmulti/package.json index b9c630d6a1..06d3acb413 100644 --- a/packages/defmulti/package.json +++ b/packages/defmulti/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/defmulti", - "version": "1.1.2", + "version": "1.2.0", "description": "Dynamically extensible multiple dispatch via user supplied dispatch function.", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/errors": "^1.2.1" }, "keywords": [ "ES6", diff --git a/packages/defmulti/src/api.ts b/packages/defmulti/src/api.ts new file mode 100644 index 0000000000..a515167e3f --- /dev/null +++ b/packages/defmulti/src/api.ts @@ -0,0 +1,311 @@ +import { + Fn, + Fn2, + Fn3, + Fn4, + Fn5, + Fn6, + Fn7, + Fn8, + FnAny, + ILogger, + IObjectOf, + NULL_LOGGER +} from "@thi.ng/api"; + +export const DEFAULT: unique symbol = Symbol(); + +export type DispatchFn = FnAny; +export type DispatchFn1 = Fn; +export type DispatchFn1O = (a: A, b?: B) => PropertyKey; +export type DispatchFn2 = Fn2; +export type DispatchFn2O = (a: A, b: B, c?: C) => PropertyKey; +export type DispatchFn3 = Fn3; +export type DispatchFn3O = (a: A, b: B, c: C, d?: D) => PropertyKey; +export type DispatchFn4 = Fn4; +export type DispatchFn4O = ( + a: A, + b: B, + c: C, + d: D, + e?: E +) => PropertyKey; +export type DispatchFn5 = Fn5; +export type DispatchFn5O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f?: F +) => PropertyKey; +export type DispatchFn6 = Fn6; +export type DispatchFn6O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g?: G +) => PropertyKey; +export type DispatchFn7 = Fn7< + A, + B, + C, + D, + E, + F, + G, + PropertyKey +>; +export type DispatchFn7O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h?: H +) => PropertyKey; +export type DispatchFn8 = Fn8< + A, + B, + C, + D, + E, + F, + G, + H, + PropertyKey +>; +export type DispatchFn8O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i?: I +) => PropertyKey; + +export type Implementation = FnAny; +export type Implementation1 = Fn; +export type Implementation1O = (a: A, b?: B) => T; +export type Implementation2 = Fn2; +export type Implementation2O = (a: A, b: B, c?: C) => T; +export type Implementation3 = Fn3; +export type Implementation3O = (a: A, b: B, c: C, d?: D) => T; +export type Implementation4 = Fn4; +export type Implementation4O = ( + a: A, + b: B, + c: C, + d: D, + e?: E +) => T; +export type Implementation5 = Fn5; +export type Implementation5O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f?: F +) => T; +export type Implementation6 = Fn6; +export type Implementation6O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g?: G +) => T; +export type Implementation7 = Fn7< + A, + B, + C, + D, + E, + F, + G, + T +>; +export type Implementation7O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h?: H +) => T; +export type Implementation8 = Fn8< + A, + B, + C, + D, + E, + F, + G, + H, + T +>; +export type Implementation8O = ( + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, + g: G, + h: H, + i?: I +) => T; + +export interface MultiFnBase { + /** + * Registers implementation for dispatch value `id`. Returns true, + * if successful. Returns false if an implementation already exists + * (and does nothing in this case). + * + * @param id + * @param impl + */ + add(id: PropertyKey, impl: I): boolean; + /** + * Takes an object of dispatch values and their implementations and + * calls `.add()` for each KV pair. Returns true, if all impls were + * added successfully. Note: Only numbers or strings are accepted as + * dispatch values here. + * + * @param impls + */ + addAll(impls: IObjectOf): boolean; + /** + * Removes implementation for dispatch value `id`. Returns true, if + * successful. + * + * @param id + */ + remove(id: PropertyKey): boolean; + /** + * Returns true, if the function is callable (has a valid + * implementation) for given arguments. + * + * @param args + */ + callable(...args: any[]): boolean; + /** + * Returns a set of all registered dispatch values. + */ + impls(): Set; + /** + * Updates dispatch hierarchy by declaring dispatch value `id` to + * delegate to `parent`'s implementation. I.e. in terms of dispatch + * logic, `id` is considered the same as `parent. + * + * @param id + * @param parent + */ + isa(id: PropertyKey, parent: PropertyKey): boolean; + /** + * Returns all known dispatch relationships. This is an object with + * all registered dispatch values as keys, each with a set of parent + * dispatch values. + */ + rels(): IObjectOf>; + /** + * Returns a set of immediate parent dispatch values for given + * dispatch value `id`. + * + * @param id + */ + parents(id: PropertyKey): Set; + /** + * Similar to `parents()`, but includes all transitive parent dispatch + * values for given dispatch value `id`. + * @param id + */ + ancestors(id: PropertyKey): Set; +} + +export interface MultiFn + extends Implementation, + MultiFnBase> {} + +export interface MultiFn1 + extends Implementation1, + MultiFnBase> {} + +export interface MultiFn1O + extends Implementation1O, + MultiFnBase> {} + +export interface MultiFn2 + extends Implementation2, + MultiFnBase> {} + +export interface MultiFn2O + extends Implementation2O, + MultiFnBase> {} + +export interface MultiFn3 + extends Implementation3, + MultiFnBase> {} + +export interface MultiFn3O + extends Implementation3O, + MultiFnBase> {} + +export interface MultiFn4 + extends Implementation4, + MultiFnBase> {} + +export interface MultiFn4O + extends Implementation4O, + MultiFnBase> {} + +export interface MultiFn5 + extends Implementation5, + MultiFnBase> {} + +export interface MultiFn5O + extends Implementation5O, + MultiFnBase> {} + +export interface MultiFn6 + extends Implementation6, + MultiFnBase> {} + +export interface MultiFn6O + extends Implementation6O, + MultiFnBase> {} + +export interface MultiFn7 + extends Implementation7, + MultiFnBase> {} + +export interface MultiFn7O + extends Implementation7O, + MultiFnBase> {} + +export interface MultiFn8 + extends Implementation8, + MultiFnBase> {} + +export interface MultiFn8O + extends Implementation8O, + MultiFnBase> {} + +export type AncestorDefs = IObjectOf>; + +export let LOGGER: ILogger = NULL_LOGGER; + +export const setLogger = (logger: ILogger) => (LOGGER = logger); diff --git a/packages/defmulti/src/defmulti-n.ts b/packages/defmulti/src/defmulti-n.ts new file mode 100644 index 0000000000..88214afecd --- /dev/null +++ b/packages/defmulti/src/defmulti-n.ts @@ -0,0 +1,51 @@ +import { illegalArity } from "@thi.ng/errors"; +import { DEFAULT, Implementation } from "./api"; +import { defmulti } from "./defmulti"; + +/** + * Returns a multi-dispatch function which delegates to one of the + * provided implementations, based on the arity (number of args) when + * the function is called. Internally uses `defmulti`, so new arities + * can be dynamically added (or removed) at a later time. If no + * `fallback` is provided, `defmultiN` also registers a `DEFAULT` + * implementation which simply throws an `IllegalArityError` when + * invoked. + * + * **Note:** Unlike `defmulti` no argument type checking is supported, + * however you can specify the return type for the generated function. + * + * ``` + * const foo = defmultiN({ + * 0: () => "zero", + * 1: (x) => `one: ${x}`, + * 3: (x, y, z) => `three: ${x}, ${y}, ${z}` + * }); + * + * foo(); + * // zero + * foo(23); + * // one: 23 + * foo(1, 2, 3); + * // three: 1, 2, 3 + * foo(1, 2); + * // Error: illegal arity: 2 + * + * foo.add(2, (x, y) => `two: ${x}, ${y}`); + * foo(1, 2); + * // two: 1, 2 + * ``` + * + * @param impls + * @param fallback + */ +export const defmultiN = ( + impls: { [id: number]: Implementation }, + fallback?: Implementation +) => { + const fn = defmulti((...args: any[]) => args.length); + fn.add(DEFAULT, fallback || ((...args) => illegalArity(args.length))); + for (let id in impls) { + fn.add(id, impls[id]); + } + return fn; +}; diff --git a/packages/defmulti/src/defmulti.ts b/packages/defmulti/src/defmulti.ts new file mode 100644 index 0000000000..7ef16cea6b --- /dev/null +++ b/packages/defmulti/src/defmulti.ts @@ -0,0 +1,191 @@ +import { IObjectOf } from "@thi.ng/api"; +import { unsupported } from "@thi.ng/errors"; +import { + AncestorDefs, + DEFAULT, + DispatchFn, + DispatchFn1, + DispatchFn1O, + DispatchFn2, + DispatchFn2O, + DispatchFn3, + DispatchFn3O, + DispatchFn4, + DispatchFn4O, + DispatchFn5, + DispatchFn5O, + DispatchFn6, + DispatchFn6O, + DispatchFn7, + DispatchFn7O, + DispatchFn8, + DispatchFn8O, + Implementation, + LOGGER, + MultiFn, + MultiFn1, + MultiFn1O, + MultiFn2, + MultiFn2O, + MultiFn3, + MultiFn3O, + MultiFn4, + MultiFn4O, + MultiFn5, + MultiFn5O, + MultiFn6, + MultiFn6O, + MultiFn7, + MultiFn7O, + MultiFn8, + MultiFn8O +} from "./api"; + +/** + * Returns a new multi-dispatch function using the provided dispatcher. + * The dispatcher can take any number of arguments and must produce a + * dispatch value (string, number or symbol) used to lookup an + * implementation. If found, the impl is called with the same args and + * its return value used as own return value. If no matching + * implementation is available, attempts to dispatch to DEFAULT impl. If + * none is registered, an error is thrown. + * + * `defmulti` provides generics for type checking up to 8 args (plus the + * return type) and the generics will also apply to all implementations. + * If more than 8 args are required, `defmulti` will fall back to an + * untyped varargs solution. + * + * Implementations for different dispatch values can be added and + * removed dynamically by calling `.add(id, fn)` or `.remove(id)` on the + * returned function. Each returns `true` if the operation was + * successful. + */ +export function defmulti(f: DispatchFn, rels?: AncestorDefs): MultiFn; +// prettier-ignore +export function defmulti(f: DispatchFn1, rels?: AncestorDefs): MultiFn1; +// prettier-ignore +export function defmulti(f: DispatchFn2, rels?: AncestorDefs): MultiFn2; +// prettier-ignore +export function defmulti(f: DispatchFn1O, rels?: AncestorDefs): MultiFn1O; +// prettier-ignore +export function defmulti(f: DispatchFn3, rels?: AncestorDefs): MultiFn3; +// prettier-ignore +export function defmulti(f: DispatchFn2O, rels?: AncestorDefs): MultiFn2O; +// prettier-ignore +export function defmulti(f: DispatchFn4, rels?: AncestorDefs): MultiFn4; +// prettier-ignore +export function defmulti(f: DispatchFn3O, rels?: AncestorDefs): MultiFn3O; +// prettier-ignore +export function defmulti(f: DispatchFn5, rels?: AncestorDefs): MultiFn5; +// prettier-ignore +export function defmulti(f: DispatchFn4O, rels?: AncestorDefs): MultiFn4O; +// prettier-ignore +export function defmulti(f: DispatchFn6, rels?: AncestorDefs): MultiFn6; +// prettier-ignore +export function defmulti(f: DispatchFn5O, rels?: AncestorDefs): MultiFn5O; +// prettier-ignore +export function defmulti(f: DispatchFn7, rels?: AncestorDefs): MultiFn7; +// prettier-ignore +export function defmulti(f: DispatchFn6O, rels?: AncestorDefs): MultiFn6O; +// prettier-ignore +export function defmulti(f: DispatchFn8, rels?: AncestorDefs): MultiFn8; +// prettier-ignore +export function defmulti(f: DispatchFn7O, rels?: AncestorDefs): MultiFn7O; +// prettier-ignore +export function defmulti(f: DispatchFn8O, rels?: AncestorDefs): MultiFn8O; +export function defmulti(f: any, ancestors?: AncestorDefs) { + const impls: IObjectOf> = {}; + const rels: IObjectOf> = ancestors + ? makeRels(ancestors) + : {}; + const fn: any = (...args: any[]) => { + const id = f(...args); + const g = impls[id] || findImpl(impls, rels, id) || impls[DEFAULT]; + return g + ? g(...args) + : unsupported(`missing implementation for: "${id.toString()}"`); + }; + fn.add = (id: PropertyKey, g: Implementation) => { + if (impls[id]) { + LOGGER.warn(`overwriting '${id.toString()}' impl`); + } + impls[id] = g; + return true; + }; + fn.addAll = (_impls: IObjectOf>) => { + let ok = true; + for (let id in _impls) { + ok = fn.add(id, _impls[id]) && ok; + } + return ok; + }; + fn.remove = (id: PropertyKey) => { + if (!impls[id]) return false; + delete impls[id]; + return true; + }; + fn.callable = (...args: any[]) => { + const id = f(...args); + return !!( + impls[id] || + findImpl(impls, rels, id) || + impls[DEFAULT] + ); + }; + fn.isa = (id: PropertyKey, parent: PropertyKey) => { + let val = rels[id]; + !val && (rels[id] = val = new Set()); + val.add(parent); + }; + fn.impls = () => { + const res = new Set(Object.keys(impls)); + for (let id in rels) { + findImpl(impls, rels, id) && res.add(id); + } + impls[DEFAULT] && res.add(DEFAULT); + return res; + }; + fn.rels = () => rels; + fn.parents = (id: PropertyKey) => rels[id]; + fn.ancestors = (id: PropertyKey) => + new Set(findAncestors([], rels, id)); + return fn; +} + +const findImpl = ( + impls: IObjectOf>, + rels: IObjectOf>, + id: PropertyKey +) => { + const parents = rels[id]; + if (!parents) return; + for (let p of parents) { + let impl: Implementation = + impls[p] || findImpl(impls, rels, p); + if (impl) return impl; + } +}; + +const findAncestors = ( + acc: PropertyKey[], + rels: IObjectOf>, + id: PropertyKey +) => { + const parents = rels[id]; + if (parents) { + for (let p of parents) { + acc.push(p); + findAncestors(acc, rels, p); + } + } + return acc; +}; + +const makeRels = (spec: AncestorDefs) => { + const rels: IObjectOf> = {}; + for (let k in spec) { + const val = spec[k]; + rels[k] = val instanceof Set ? val : new Set(val); + } + return rels; +}; diff --git a/packages/defmulti/src/impls.ts b/packages/defmulti/src/impls.ts new file mode 100644 index 0000000000..97be4b1646 --- /dev/null +++ b/packages/defmulti/src/impls.ts @@ -0,0 +1,76 @@ +import { IObjectOf } from "@thi.ng/api"; +import { illegalArgs } from "@thi.ng/errors"; +import { Implementation, MultiFn } from "./api"; + +/** + * Syntax-sugar intended for sets of multi-methods sharing same dispatch + * values / logic. Takes a dispatch value, an object of "is-a" + * relationships and a number of multi-methods, each with an + * implementation for the given dispatch value. + * + * The relations object has dispatch values (parents) as keys and arrays + * of multi-methods as their values. For each multi-method associates + * the given `type` with the related parent dispatch value to delegate + * to its implementation. + * + * The remaining implementations are associated with their related + * multi-method and the given `type` dispatch value. + * + * ``` + * foo = defmulti((x) => x.id); + * bar = defmulti((x) => x.id); + * bax = defmulti((x) => x.id); + * baz = defmulti((x) => x.id); + * + * // define impls for dispatch value `a` + * implementations( + * "a", + * + * // delegate bax & baz impls to dispatch val `b` + * { + * b: [bax, baz] + * }, + * + * // concrete multi-fn impls + * foo, + * (x) => `foo: ${x.val}`, + * + * bar, + * (x) => `bar: ${x.val.toUpperCase()}` + * ); + * + * // add parent impls + * bax.add("b", (x) => `bax: ${x.id}`); + * baz.add("c", (x) => `baz: ${x.id}`); + * // use "c" impl for "b" + * baz.isa("b", "c"); + * + * foo({ id: "a", val: "alice" }); // "foo: alice" + * bar({ id: "a", val: "alice" }); // "bar: ALICE" + * bax({ id: "a", val: "alice" }); // "bax: a" + * baz({ id: "a", val: "alice" }); // "baz: a" + * + * baz.impls(); // Set { "c", "a", "b" } + * ``` + * + * @param type + * @param impls + */ +export const implementations = ( + type: PropertyKey, + rels: IObjectOf[]>, + ...impls: (MultiFn | Implementation)[] +) => { + impls.length & 1 && + illegalArgs("expected an even number of implementation items"); + if (rels) { + for (let parent in rels) { + for (let fn of rels[parent]) { + fn.isa(type, parent); + } + } + } + for (let i = 0; i < impls.length; i += 2) { + (>impls[i]).add(type, impls[i + 1]); + } +}; diff --git a/packages/defmulti/src/index.ts b/packages/defmulti/src/index.ts index 75c6df6e56..5cdab3f896 100644 --- a/packages/defmulti/src/index.ts +++ b/packages/defmulti/src/index.ts @@ -1,606 +1,4 @@ -import { - Fn, - Fn2, - Fn3, - Fn4, - Fn5, - Fn6, - Fn7, - Fn8, - FnAny, - IObjectOf -} from "@thi.ng/api"; -import { illegalArgs, illegalArity, unsupported } from "@thi.ng/errors"; - -export const DEFAULT: unique symbol = Symbol(); - -export type DispatchFn = FnAny; -export type DispatchFn1 = Fn; -export type DispatchFn1O = (a: A, b?: B) => PropertyKey; -export type DispatchFn2 = Fn2; -export type DispatchFn2O = (a: A, b: B, c?: C) => PropertyKey; -export type DispatchFn3 = Fn3; -export type DispatchFn3O = (a: A, b: B, c: C, d?: D) => PropertyKey; -export type DispatchFn4 = Fn4; -export type DispatchFn4O = ( - a: A, - b: B, - c: C, - d: D, - e?: E -) => PropertyKey; -export type DispatchFn5 = Fn5; -export type DispatchFn5O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f?: F -) => PropertyKey; -export type DispatchFn6 = Fn6; -export type DispatchFn6O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g?: G -) => PropertyKey; -export type DispatchFn7 = Fn7< - A, - B, - C, - D, - E, - F, - G, - PropertyKey ->; -export type DispatchFn7O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h?: H -) => PropertyKey; -export type DispatchFn8 = Fn8< - A, - B, - C, - D, - E, - F, - G, - H, - PropertyKey ->; -export type DispatchFn8O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i?: I -) => PropertyKey; - -export type Implementation = FnAny; -export type Implementation1 = Fn; -export type Implementation1O = (a: A, b?: B) => T; -export type Implementation2 = Fn2; -export type Implementation2O = (a: A, b: B, c?: C) => T; -export type Implementation3 = Fn3; -export type Implementation3O = (a: A, b: B, c: C, d?: D) => T; -export type Implementation4 = Fn4; -export type Implementation4O = ( - a: A, - b: B, - c: C, - d: D, - e?: E -) => T; -export type Implementation5 = Fn5; -export type Implementation5O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f?: F -) => T; -export type Implementation6 = Fn6; -export type Implementation6O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g?: G -) => T; -export type Implementation7 = Fn7< - A, - B, - C, - D, - E, - F, - G, - T ->; -export type Implementation7O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h?: H -) => T; -export type Implementation8 = Fn8< - A, - B, - C, - D, - E, - F, - G, - H, - T ->; -export type Implementation8O = ( - a: A, - b: B, - c: C, - d: D, - e: E, - f: F, - g: G, - h: H, - i?: I -) => T; - -export interface MultiFnBase { - /** - * Registers implementation for dispatch value `id`. Returns true, - * if successful. Returns false if an implementation already exists - * (and does nothing in this case). - * - * @param id - * @param impl - */ - add(id: PropertyKey, impl: I): boolean; - /** - * Takes an object of dispatch values and their implementations and - * calls `.add()` for each KV pair. Returns true, if all impls were - * added successfully. Note: Only numbers or strings are accepted as - * dispatch values here. - * - * @param impls - */ - addAll(impls: IObjectOf): boolean; - /** - * Removes implementation for dispatch value `id`. Returns true, if - * successful. - * - * @param id - */ - remove(id: PropertyKey): boolean; - /** - * Returns true, if the function is callable (has a valid - * implementation) for given arguments. - * - * @param args - */ - callable(...args: any[]): boolean; - /** - * Returns a set of all registered dispatch values. - */ - impls(): Set; - /** - * Updates dispatch hierarchy by declaring dispatch value `id` to - * delegate to `parent`'s implementation. I.e. in terms of dispatch - * logic, `id` is considered the same as `parent. - * - * @param id - * @param parent - */ - isa(id: PropertyKey, parent: PropertyKey): boolean; - /** - * Returns all known dispatch relationships. This is an object with - * all registered dispatch values as keys, each with a set of parent - * dispatch values. - */ - rels(): IObjectOf>; - /** - * Returns a set of immediate parent dispatch values for given - * dispatch value `id`. - * - * @param id - */ - parents(id: PropertyKey): Set; - /** - * Similar to `parents()`, but includes all transitive parent dispatch - * values for given dispatch value `id`. - * @param id - */ - ancestors(id: PropertyKey): Set; -} - -export interface MultiFn - extends Implementation, - MultiFnBase> {} - -export interface MultiFn1 - extends Implementation1, - MultiFnBase> {} - -export interface MultiFn1O - extends Implementation1O, - MultiFnBase> {} - -export interface MultiFn2 - extends Implementation2, - MultiFnBase> {} - -export interface MultiFn2O - extends Implementation2O, - MultiFnBase> {} - -export interface MultiFn3 - extends Implementation3, - MultiFnBase> {} - -export interface MultiFn3O - extends Implementation3O, - MultiFnBase> {} - -export interface MultiFn4 - extends Implementation4, - MultiFnBase> {} - -export interface MultiFn4O - extends Implementation4O, - MultiFnBase> {} - -export interface MultiFn5 - extends Implementation5, - MultiFnBase> {} - -export interface MultiFn5O - extends Implementation5O, - MultiFnBase> {} - -export interface MultiFn6 - extends Implementation6, - MultiFnBase> {} - -export interface MultiFn6O - extends Implementation6O, - MultiFnBase> {} - -export interface MultiFn7 - extends Implementation7, - MultiFnBase> {} - -export interface MultiFn7O - extends Implementation7O, - MultiFnBase> {} - -export interface MultiFn8 - extends Implementation8, - MultiFnBase> {} - -export interface MultiFn8O - extends Implementation8O, - MultiFnBase> {} - -export type AncestorDefs = IObjectOf>; - -/** - * Returns a new multi-dispatch function using the provided dispatcher. - * The dispatcher can take any number of arguments and must produce a - * dispatch value (string, number or symbol) used to lookup an - * implementation. If found, the impl is called with the same args and - * its return value used as own return value. If no matching - * implementation is available, attempts to dispatch to DEFAULT impl. If - * none is registered, an error is thrown. - * - * `defmulti` provides generics for type checking up to 8 args (plus the - * return type) and the generics will also apply to all implementations. - * If more than 8 args are required, `defmulti` will fall back to an - * untyped varargs solution. - * - * Implementations for different dispatch values can be added and - * removed dynamically by calling `.add(id, fn)` or `.remove(id)` on the - * returned function. Each returns `true` if the operation was - * successful. - */ -export function defmulti(f: DispatchFn, rels?: AncestorDefs): MultiFn; -export function defmulti( - f: DispatchFn1, - rels?: AncestorDefs -): MultiFn1; -export function defmulti( - f: DispatchFn2, - rels?: AncestorDefs -): MultiFn2; -export function defmulti( - f: DispatchFn1O, - rels?: AncestorDefs -): MultiFn1O; -export function defmulti( - f: DispatchFn3, - rels?: AncestorDefs -): MultiFn3; -export function defmulti( - f: DispatchFn2O, - rels?: AncestorDefs -): MultiFn2O; -export function defmulti( - f: DispatchFn4, - rels?: AncestorDefs -): MultiFn4; -export function defmulti( - f: DispatchFn3O, - rels?: AncestorDefs -): MultiFn3O; -export function defmulti( - f: DispatchFn5, - rels?: AncestorDefs -): MultiFn5; -export function defmulti( - f: DispatchFn4O, - rels?: AncestorDefs -): MultiFn4O; -export function defmulti( - f: DispatchFn6, - rels?: AncestorDefs -): MultiFn6; -export function defmulti( - f: DispatchFn5O, - rels?: AncestorDefs -): MultiFn5O; -export function defmulti( - f: DispatchFn7, - rels?: AncestorDefs -): MultiFn7; -export function defmulti( - f: DispatchFn6O, - rels?: AncestorDefs -): MultiFn6O; -export function defmulti( - f: DispatchFn8, - rels?: AncestorDefs -): MultiFn8; -export function defmulti( - f: DispatchFn7O, - rels?: AncestorDefs -): MultiFn7O; -export function defmulti( - f: DispatchFn8O, - rels?: AncestorDefs -): MultiFn8O; -export function defmulti(f: any, ancestors?: AncestorDefs) { - const impls: IObjectOf> = {}; - const rels: IObjectOf> = ancestors - ? makeRels(ancestors) - : {}; - const fn: any = (...args: any[]) => { - const id = f(...args); - const g = impls[id] || findImpl(impls, rels, id) || impls[DEFAULT]; - return g - ? g(...args) - : unsupported(`missing implementation for: "${id.toString()}"`); - }; - fn.add = (id: PropertyKey, g: Implementation) => { - if (impls[id]) return false; - impls[id] = g; - return true; - }; - fn.addAll = (_impls: IObjectOf>) => { - let ok = true; - for (let id in _impls) { - ok = fn.add(id, _impls[id]) && ok; - } - return ok; - }; - fn.remove = (id: PropertyKey) => { - if (!impls[id]) return false; - delete impls[id]; - return true; - }; - fn.callable = (...args: any[]) => { - const id = f(...args); - return !!( - impls[id] || - findImpl(impls, rels, id) || - impls[DEFAULT] - ); - }; - fn.isa = (id: PropertyKey, parent: PropertyKey) => { - let val = rels[id]; - !val && (rels[id] = val = new Set()); - val.add(parent); - }; - fn.impls = () => { - const res = new Set(Object.keys(impls)); - for (let id in rels) { - findImpl(impls, rels, id) && res.add(id); - } - impls[DEFAULT] && res.add(DEFAULT); - return res; - }; - fn.rels = () => rels; - fn.parents = (id: PropertyKey) => rels[id]; - fn.ancestors = (id: PropertyKey) => - new Set(findAncestors([], rels, id)); - return fn; -} - -const findImpl = ( - impls: IObjectOf>, - rels: IObjectOf>, - id: PropertyKey -) => { - const parents = rels[id]; - if (!parents) return; - for (let p of parents) { - let impl: Implementation = - impls[p] || findImpl(impls, rels, p); - if (impl) return impl; - } -}; - -const findAncestors = ( - acc: PropertyKey[], - rels: IObjectOf>, - id: PropertyKey -) => { - const parents = rels[id]; - if (parents) { - for (let p of parents) { - acc.push(p); - findAncestors(acc, rels, p); - } - } - return acc; -}; - -const makeRels = (spec: AncestorDefs) => { - const rels: IObjectOf> = {}; - for (let k in spec) { - const val = spec[k]; - rels[k] = val instanceof Set ? val : new Set(val); - } - return rels; -}; - -/** - * Returns a multi-dispatch function which delegates to one of the - * provided implementations, based on the arity (number of args) when - * the function is called. Internally uses `defmulti`, so new arities - * can be dynamically added (or removed) at a later time. If no - * `fallback` is provided, `defmultiN` also registers a `DEFAULT` - * implementation which simply throws an `IllegalArityError` when - * invoked. - * - * **Note:** Unlike `defmulti` no argument type checking is supported, - * however you can specify the return type for the generated function. - * - * ``` - * const foo = defmultiN({ - * 0: () => "zero", - * 1: (x) => `one: ${x}`, - * 3: (x, y, z) => `three: ${x}, ${y}, ${z}` - * }); - * - * foo(); - * // zero - * foo(23); - * // one: 23 - * foo(1, 2, 3); - * // three: 1, 2, 3 - * foo(1, 2); - * // Error: illegal arity: 2 - * - * foo.add(2, (x, y) => `two: ${x}, ${y}`); - * foo(1, 2); - * // two: 1, 2 - * ``` - * - * @param impls - * @param fallback - */ -export const defmultiN = ( - impls: { [id: number]: Implementation }, - fallback?: Implementation -) => { - const fn = defmulti((...args: any[]) => args.length); - fn.add(DEFAULT, fallback || ((...args) => illegalArity(args.length))); - for (let id in impls) { - fn.add(id, impls[id]); - } - return fn; -}; - -/** - * Syntax-sugar intended for sets of multi-methods sharing same dispatch - * values / logic. Takes a dispatch value, an object of "is-a" - * relationships and a number of multi-methods, each with an - * implementation for the given dispatch value. - * - * The relations object has dispatch values (parents) as keys and arrays - * of multi-methods as their values. For each multi-method associates - * the given `type` with the related parent dispatch value to delegate - * to its implementation. - * - * The remaining implementations are associated with their related - * multi-method and the given `type` dispatch value. - * - * ``` - * foo = defmulti((x) => x.id); - * bar = defmulti((x) => x.id); - * bax = defmulti((x) => x.id); - * baz = defmulti((x) => x.id); - * - * // define impls for dispatch value `a` - * implementations( - * "a", - * - * // delegate bax & baz impls to dispatch val `b` - * { - * b: [bax, baz] - * }, - * - * // concrete multi-fn impls - * foo, - * (x) => `foo: ${x.val}`, - * - * bar, - * (x) => `bar: ${x.val.toUpperCase()}` - * ); - * - * // add parent impls - * bax.add("b", (x) => `bax: ${x.id}`); - * baz.add("c", (x) => `baz: ${x.id}`); - * // use "c" impl for "b" - * baz.isa("b", "c"); - * - * foo({ id: "a", val: "alice" }); // "foo: alice" - * bar({ id: "a", val: "alice" }); // "bar: ALICE" - * bax({ id: "a", val: "alice" }); // "bax: a" - * baz({ id: "a", val: "alice" }); // "baz: a" - * - * baz.impls(); // Set { "c", "a", "b" } - * ``` - * - * @param type - * @param impls - */ -export const implementations = ( - type: PropertyKey, - rels: IObjectOf[]>, - ...impls: (MultiFn | Implementation)[] -) => { - impls.length & 1 && - illegalArgs("expected an even number of implementation items"); - if (rels) { - for (let parent in rels) { - for (let fn of rels[parent]) { - fn.isa(type, parent); - } - } - } - for (let i = 0; i < impls.length; i += 2) { - (>impls[i]).add(type, impls[i + 1]); - } -}; +export * from "./api"; +export * from "./defmulti"; +export * from "./defmulti-n"; +export * from "./impls"; diff --git a/packages/defmulti/test/index.ts b/packages/defmulti/test/index.ts index 7467d96970..cbdc8305d2 100644 --- a/packages/defmulti/test/index.ts +++ b/packages/defmulti/test/index.ts @@ -1,9 +1,11 @@ +import { ConsoleLogger } from "@thi.ng/api"; import * as assert from "assert"; import { DEFAULT, defmulti, defmultiN, - implementations + implementations, + setLogger } from "../src/index"; // prettier-ignore @@ -26,10 +28,14 @@ describe("defmulti", () => { assert(exec.add("+", ([_, ...args]) => args.reduce((acc: number, n: any) => acc + exec(n), 0))); assert(exec.add("*", ([_, ...args]) => args.reduce((acc: number, n: any) => acc * exec(n), 1))); assert(exec.add("number", (x) => x)); - assert(!exec.add("number", (x) => x)); assert(exec.add(DEFAULT, (x) => { throw new Error(`invalid expr: ${x}`); })); assert.equal(exec(["+", ["*", 10, ["+", 1, 2, 3]], 6]), 66); + + setLogger(new ConsoleLogger("defmulti")); + assert(exec.add("number", (x) => x * 2)); + assert.equal(exec(["+", ["*", 10, ["+", 1, 2, 3]], 6]), ((1*2 + 2*2 + 3*2) * 10*2) + 6*2); + assert.throws(() => exec("")); }); diff --git a/packages/dgraph/CHANGELOG.md b/packages/dgraph/CHANGELOG.md index 3ac800d09a..ff7fc7bf6c 100644 --- a/packages/dgraph/CHANGELOG.md +++ b/packages/dgraph/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.16](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@1.1.15...@thi.ng/dgraph@1.1.16) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/dgraph + + + + + +## [1.1.15](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@1.1.14...@thi.ng/dgraph@1.1.15) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/dgraph + + + + + +## [1.1.14](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@1.1.13...@thi.ng/dgraph@1.1.14) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/dgraph + + + + + +## [1.1.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@1.1.12...@thi.ng/dgraph@1.1.13) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/dgraph + + + + + ## [1.1.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@1.1.11...@thi.ng/dgraph@1.1.12) (2019-07-31) **Note:** Version bump only for package @thi.ng/dgraph diff --git a/packages/dgraph/package.json b/packages/dgraph/package.json index c06d3a6d4f..362183f95e 100644 --- a/packages/dgraph/package.json +++ b/packages/dgraph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dgraph", - "version": "1.1.12", + "version": "1.1.16", "description": "Type-agnostic directed acyclic graph (DAG) & graph operations", "module": "./index.js", "main": "./lib/index.js", @@ -29,15 +29,15 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/associative": "^2.4.2", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/associative": "^3.1.0", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "data structure", diff --git a/packages/diff/CHANGELOG.md b/packages/diff/CHANGELOG.md index 3970cfb10e..0a97834cd7 100644 --- a/packages/diff/CHANGELOG.md +++ b/packages/diff/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/diff@3.2.4...@thi.ng/diff@3.2.5) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/diff + + + + + +## [3.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/diff@3.2.3...@thi.ng/diff@3.2.4) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/diff + + + + + +## [3.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/diff@3.2.2...@thi.ng/diff@3.2.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/diff + + + + + ## [3.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/diff@3.2.1...@thi.ng/diff@3.2.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/diff diff --git a/packages/diff/package.json b/packages/diff/package.json index 03e76d64f5..8c26dbd130 100644 --- a/packages/diff/package.json +++ b/packages/diff/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/diff", - "version": "3.2.2", + "version": "3.2.5", "description": "Array & object Diff", "module": "./index.js", "main": "./lib/index.js", @@ -28,12 +28,12 @@ "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/equiv": "^1.0.9" + "@thi.ng/api": "^6.5.0", + "@thi.ng/equiv": "^1.0.10" }, "keywords": [ "additions", diff --git a/packages/dlogic/CHANGELOG.md b/packages/dlogic/CHANGELOG.md index 0c997cf80a..bfb0d9eef6 100644 --- a/packages/dlogic/CHANGELOG.md +++ b/packages/dlogic/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/dlogic@1.0.9...@thi.ng/dlogic@1.0.10) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/dlogic + + + + + ## [1.0.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/dlogic@1.0.8...@thi.ng/dlogic@1.0.9) (2019-07-31) **Note:** Version bump only for package @thi.ng/dlogic diff --git a/packages/dlogic/package.json b/packages/dlogic/package.json index 8c78f107a0..7dd34f9b74 100644 --- a/packages/dlogic/package.json +++ b/packages/dlogic/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dlogic", - "version": "1.0.9", + "version": "1.0.10", "description": "Assorted digital logic ops / constructs.", "module": "./index.js", "main": "./lib/index.js", @@ -29,8 +29,8 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "keywords": [ "boolean", diff --git a/packages/dot/CHANGELOG.md b/packages/dot/CHANGELOG.md index 2bc462bd27..8103725a16 100644 --- a/packages/dot/CHANGELOG.md +++ b/packages/dot/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/dot@1.1.5...@thi.ng/dot@1.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/dot + + + + + +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/dot@1.1.4...@thi.ng/dot@1.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/dot + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/dot@1.1.3...@thi.ng/dot@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/dot + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/dot@1.1.2...@thi.ng/dot@1.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/dot + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/dot@1.1.1...@thi.ng/dot@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/dot diff --git a/packages/dot/README.md b/packages/dot/README.md index 23fdb3f747..d271958d49 100644 --- a/packages/dot/README.md +++ b/packages/dot/README.md @@ -34,7 +34,7 @@ yarn add @thi.ng/dot ## Usage examples -![example graph](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/dot-example.png) +![example graph](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/dot/dot-example.png) The source code of this example is also available in [/test/example.ts](https://github.com/thi-ng/umbrella/tree/master/packages/dot/test/example.ts). diff --git a/packages/dot/package.json b/packages/dot/package.json index c909a33976..b662ed3eab 100644 --- a/packages/dot/package.json +++ b/packages/dot/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dot", - "version": "1.1.2", + "version": "1.1.6", "description": "Graphviz DOM abstraction as vanilla JS objects & serialization to DOT format", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1" }, "keywords": [ "ES6", diff --git a/packages/dsp/CHANGELOG.md b/packages/dsp/CHANGELOG.md index 8c3dbe1731..37f8b25cf9 100644 --- a/packages/dsp/CHANGELOG.md +++ b/packages/dsp/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.17](https://github.com/thi-ng/umbrella/compare/@thi.ng/dsp@1.0.16...@thi.ng/dsp@1.0.17) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/dsp + + + + + +## [1.0.16](https://github.com/thi-ng/umbrella/compare/@thi.ng/dsp@1.0.15...@thi.ng/dsp@1.0.16) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/dsp + + + + + +## [1.0.15](https://github.com/thi-ng/umbrella/compare/@thi.ng/dsp@1.0.14...@thi.ng/dsp@1.0.15) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/dsp + + + + + ## [1.0.14](https://github.com/thi-ng/umbrella/compare/@thi.ng/dsp@1.0.13...@thi.ng/dsp@1.0.14) (2019-07-31) **Note:** Version bump only for package @thi.ng/dsp diff --git a/packages/dsp/package.json b/packages/dsp/package.json index 9f3221dbbd..ebcb79a9bf 100644 --- a/packages/dsp/package.json +++ b/packages/dsp/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dsp", - "version": "1.0.14", + "version": "1.0.17", "description": "Assorted DSP utils, oscillators etc.", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/math": "^1.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/math": "^1.5.0" }, "keywords": [ "additive", diff --git a/packages/ecs/.npmignore b/packages/ecs/.npmignore new file mode 100644 index 0000000000..24f388daa6 --- /dev/null +++ b/packages/ecs/.npmignore @@ -0,0 +1,18 @@ +.cache +.meta +.nyc_output +*.gz +*.html +*.svg +*.tgz +*.h +*.o +*.wasm +build +coverage +dev +doc +export +src* +test +tsconfig.json diff --git a/packages/ecs/CHANGELOG.md b/packages/ecs/CHANGELOG.md new file mode 100644 index 0000000000..a78e1b5cd2 --- /dev/null +++ b/packages/ecs/CHANGELOG.md @@ -0,0 +1,19 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.1.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/ecs@0.0.2...@thi.ng/ecs@0.1.0) (2019-11-09) + + +### Features + +* **ecs:** add componentsForID/groupsForID(), add NullCache ([416a8b7](https://github.com/thi-ng/umbrella/commit/416a8b7974716ec8b645dde8d2ed6ad389f18edb)) +* **ecs:** add defComponent, fix return types ([8a65446](https://github.com/thi-ng/umbrella/commit/8a654463af1721377aa3372e21d86ec880548c84)) +* **ecs:** add ECS INotify impl, entity ops, update Group ([0423f35](https://github.com/thi-ng/umbrella/commit/0423f35b7f589056ee3578d32530023a318322c0)) +* **ecs:** add ECS main class, update types, Component, Group ([40dc1b6](https://github.com/thi-ng/umbrella/commit/40dc1b6abcfd0f11e04c7f7f22359bc928a9ff7d)) +* **ecs:** add generics for Component & Group related types & methods ([82e3e92](https://github.com/thi-ng/umbrella/commit/82e3e92fe6f74395383069d370e3d6eb21982da5)) +* **ecs:** add UnboundedCache, update Component & Group ctors/opts ([5c36892](https://github.com/thi-ng/umbrella/commit/5c36892ef9ed62f973a726277750c5845c9a859e)) +* **ecs:** add/update types, new components, update Group, ECS, add tests ([fdae8a7](https://github.com/thi-ng/umbrella/commit/fdae8a794093e42f71165f7552231d9af744dfcd)) +* **ecs:** initial refactor & import as new package (MBP2010) ([ad0b566](https://github.com/thi-ng/umbrella/commit/ad0b56629dc6133b3bcde429fa7df26f627ba0c1)) +* **ecs:** update Group, Component, cache behavior, IDGen, iteration ([e8c72d5](https://github.com/thi-ng/umbrella/commit/e8c72d587e58ad6dbc7e6961e6daa098b5b7e614)) diff --git a/packages/ecs/LICENSE b/packages/ecs/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/ecs/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/ecs/README.md b/packages/ecs/README.md new file mode 100644 index 0000000000..61cef5497a --- /dev/null +++ b/packages/ecs/README.md @@ -0,0 +1,139 @@ +# @thi.ng/ecs + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/ecs.svg)](https://www.npmjs.com/package/@thi.ng/ecs) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/ecs.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + +- [About](#about) + - [Status](#status) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) + - [Basic concepts](#basic-concepts) +- [Authors](#authors) +- [License](#license) + + + +## About + +Entity Component System based on memory mapped buffers & sparse sets. + +- Entities are merely numeric identifiers +- Component types: + - Memory mapped (typed array views, customizable striding) + - JS objects +- Component grouping w/ optional group ownership to allow re-ordering + components for optimized iteration +- Systems are plain functions +- Configurable caching of component views: LRU, Unbounded, Null (no-cache) + +### Status + +ALPHA - WIP + +## Installation + +```bash +yarn add @thi.ng/ecs +``` + +## Dependencies + +- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) +- [@thi.ng/associative](https://github.com/thi-ng/umbrella/tree/master/packages/associative) +- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/master/packages/checks) +- [@thi.ng/dcons](https://github.com/thi-ng/umbrella/tree/master/packages/dcons) +- [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) + +## Usage examples + +![100k particle system](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/soa-ecs-100k.png) + +See the ECS particle system example for usage/reference: + +[Live version](https://demo.thi.ng/umbrella/soa-ecs/) | +[Source code](https://github.com/thi-ng/umbrella/tree/master/examples/soa-ecs/) + +### Basic concepts + +```ts +import { ECS } from "@thi.ng/ecs"; + +interface ComSpecs { + pos: Float32Array; + vel: Float32Array; + color: string; +} + +// init ECS w/ given max number of entities +const ecs = new ECS(1000); + +// define components (and their memory layout) +const pos = ecs.defComponent({ + id: "pos", + type: Type.F32, + size: 2 +}); + +const vel = ecs.defComponent({ + id: "vel", + type: Type.F32, + size: 2, + stride: 4 + default: () => [Math.random()*2-1, Math.random()*2-1] +}); + +// this component stores string values (not mem-mapped) +const color = ecs.defComponent({ + id: "color", + default: () => ["red","green","blue"][(Math.random()*3)|0] +}); + +// define group of given components +// the group will obtain ownership of all by default, meaning +// it is allowed to re-order entities to optimize iteration performance +const group = ecs.defGroup([pos, vel, color]); + +// add entities and associate them w/ different components +// if a component is part of a group, the group will be notified/updated +ecs.defEntity(["pos", "vel", "color"]); + +ecs.defEntity([pos, vel]); + +ecs.defEntity({ + pos: [1, 2], + vel: [-1, 0], + color: "red" +}); + +// apply given function to each entity in the group +// note: entity (id=1) is NOT part of the group, +// since it doesn't have a `color` component... +group.forEach((x) => console.log(x)); +// { +// id: 0, +// color: 'green', +// vel: Float32Array [ 0.16836269199848175, -0.36699679493904114 ], +// pos: Float32Array [ 0, 0 ] +// } +// { +// id: 2, +// color: 'blue', +// vel: Float32Array [ -0.7642428278923035, -0.43176573514938354 ], +// pos: Float32Array [ 0, 0 ] +// } +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2019 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/ecs/package.json b/packages/ecs/package.json new file mode 100644 index 0000000000..5871e5d98b --- /dev/null +++ b/packages/ecs/package.json @@ -0,0 +1,53 @@ +{ + "name": "@thi.ng/ecs", + "version": "0.1.0", + "description": "TODO", + "module": "./index.js", + "main": "./lib/index.js", + "umd:main": "./lib/index.umd.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/ecs", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module", + "build:release": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module all", + "build:es6": "tsc --declaration", + "build:test": "rimraf build && tsc -p test/tsconfig.json", + "test": "yarn build:test && mocha build/test/*.js", + "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib", + "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", + "pub": "yarn build:release && yarn publish --access public" + }, + "devDependencies": { + "@thi.ng/equiv": "^1.0.10", + "@types/mocha": "^5.2.6", + "@types/node": "^12.6.3", + "mocha": "^6.1.4", + "nyc": "^14.0.0", + "typedoc": "^0.15.0", + "typescript": "^3.6.4" + }, + "dependencies": { + "@thi.ng/api": "^6.5.0", + "@thi.ng/associative": "^3.1.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/dcons": "^2.1.6", + "@thi.ng/soa": "^0.1.0", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vectors": "^4.0.0" + }, + "keywords": [ + "ES6", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "sideEffects": false +} diff --git a/packages/ecs/src/api.ts b/packages/ecs/src/api.ts new file mode 100644 index 0000000000..91334c3626 --- /dev/null +++ b/packages/ecs/src/api.ts @@ -0,0 +1,81 @@ +import { + ArrayLikeIterable, + Fn0, + IID, + INotify, + IRelease, + Type, + TypedArray, + UIntArray +} from "@thi.ng/api"; + +export const EVENT_ADDED = "added"; +export const EVENT_PRE_DELETE = "pre-delete"; +export const EVENT_CHANGED = "changed"; + +export type ComponentID = keyof S & string; + +export type ComponentDefaultValue = T | Fn0; + +export type GroupTuple> = Pick & + IID; + +export type GroupInfo> = { + [P in K]: ComponentInfo; +}; + +export interface ComponentInfo> { + values: SPEC[K] extends TypedArray ? SPEC[K] : SPEC[K][]; + size: number; + stride: number; +} + +export interface IComponent extends IID, INotify { + dense: UIntArray; + sparse: UIntArray; + vals: ArrayLike; + size: number; + stride: number; + + owner?: IID; + + has(id: number): boolean; + add(id: number, val?: V): boolean; + delete(id: number): boolean; + get(id: number): T | undefined; + getIndex(i: number): T | undefined; + + keys(): ArrayLikeIterable; + values(): IterableIterator; + + swapIndices(a: number, b: number): boolean; +} + +export interface MemMappedComponentOpts { + id: ID; + type: Type; + buf?: ArrayBuffer; + byteOffset?: number; + size?: number; + stride?: number; + default?: ComponentDefaultValue>; + cache?: ICache; +} + +export interface ObjectComponentOpts { + id: ID; + default?: ComponentDefaultValue; +} + +export interface GroupOpts { + id: string; + cache?: ICache; +} + +export interface ICache extends IRelease { + keys(): Iterable; + set(key: number, val: T): T; + get(key: number): T | undefined; + getSet(key: number, notFound: Fn0): T; + delete(key: number): boolean; +} diff --git a/packages/ecs/src/component-mm.ts b/packages/ecs/src/component-mm.ts new file mode 100644 index 0000000000..e385d283ce --- /dev/null +++ b/packages/ecs/src/component-mm.ts @@ -0,0 +1,207 @@ +import { + Event, + Fn, + IID, + INotify, + INotifyMixin, + Type, + typedArray, + TypedArray, + UIntArray +} from "@thi.ng/api"; +import { isFunction } from "@thi.ng/checks"; +import { + ComponentDefaultValue, + EVENT_ADDED, + EVENT_CHANGED, + EVENT_PRE_DELETE, + ICache, + IComponent, + MemMappedComponentOpts +} from "./api"; + +@INotifyMixin +export class MemMappedComponent + implements IComponent>, INotify { + readonly id: K; + + sparse: UIntArray; + dense: UIntArray; + vals: TypedArray; + n: number; + + readonly size: number; + readonly stride: number; + default?: ComponentDefaultValue>; + + owner?: IID; + + cache?: ICache; + + constructor( + sparse: UIntArray, + dense: UIntArray, + opts: MemMappedComponentOpts + ) { + this.sparse = sparse; + this.dense = dense; + opts = { + type: Type.F32, + size: 1, + byteOffset: 0, + ...opts + }; + this.id = opts.id; + this.size = opts.size!; + this.stride = opts.stride || this.size; + this.default = opts.default; // || zeroes(this.size); + this.vals = opts.buf + ? typedArray( + opts.type!, + opts.buf, + opts.byteOffset!, + dense.length * this.stride + ) + : typedArray(opts.type!, dense.length * this.stride); + this.cache = opts.cache; + this.n = 0; + } + + keys() { + return this.dense.slice(0, this.n); + } + + *values() { + for (let i = this.n; --i >= 0; ) { + yield this.getIndex(i)!; + } + } + + packedValues() { + return this.vals.subarray(0, this.n * this.stride); + } + + // TODO add version support via IDGen + add(id: number, val?: ArrayLike) { + const { dense, sparse, n } = this; + const max = dense.length; + const i = sparse[id]; + if (id < max && n < max && !(i < n && dense[i] === id)) { + dense[n] = id; + sparse[id] = n; + const def = this.default; + const initVal = val || (isFunction(def) ? def() : def); + initVal && this.vals.set(initVal, n * this.stride); + this.n++; + this.notify({ id: EVENT_ADDED, target: this, value: id }); + return true; + } + return false; + } + + delete(id: number) { + let { dense, sparse, n } = this; + let i = sparse[id]; + if (i < n && dense[i] === id) { + // notify listeners prior to removal to allow restructure / swaps + this.notify({ id: EVENT_PRE_DELETE, target: this, value: id }); + // get possibly updated slot + i = sparse[id]; + const j = dense[--n]; + dense[i] = j; + sparse[j] = i; + this.n = n; + const s = this.stride; + n *= s; + this.vals.copyWithin(i * s, n, n + this.size); + this.cache && this.cache.delete(i); + return true; + } + return false; + } + + has(id: number): boolean { + const i = this.sparse[id]; + return i < this.n && this.dense[i] === id; + } + + get(id: number) { + let i = this.sparse[id]; + return i < this.n && this.dense[i] === id + ? this.cache + ? this.cache.getSet(i, () => { + i *= this.stride; + return this.vals.subarray(i, i + this.size); + }) + : ((i *= this.stride), this.vals.subarray(i, i + this.size)) + : undefined; + } + + getIndex(i: number) { + return i < this.n + ? this.cache + ? this.cache.getSet(i, () => { + i *= this.stride; + return this.vals.subarray(i, i + this.size); + }) + : ((i *= this.stride), this.vals.subarray(i, i + this.size)) + : undefined; + } + + set(id: number, val: ArrayLike) { + let i = this.sparse[id]; + if (i < this.n && this.dense[i] === id) { + this.vals.set(val, i * this.stride); + this.notifyChange(id); + return true; + } + return false; + } + + setIndex(i: number, val: ArrayLike) { + return this.set(this.dense[i], val); + } + + /** + * Swaps slots of `src` & `dest` indices. The given args are NOT + * entity IDs, but indices in the `dense` array. The corresponding + * sparse & value slots are swapped too. Returns true if swap + * happened (false, if `src` and `dest` are equal) + * + * @param src + * @param dest + */ + swapIndices(src: number, dest: number) { + if (src === dest) return false; + const { dense, sparse, vals, size, stride } = this; + const ss = dense[src]; + const sd = dense[dest]; + dense[src] = sd; + dense[dest] = ss; + sparse[ss] = dest; + sparse[sd] = src; + src *= stride; + dest *= stride; + const tmp = vals.slice(src, src + size); + vals.copyWithin(src, dest, dest + size); + vals.set(tmp, dest); + return true; + } + + // @ts-ignore: arguments + addListener(id: string, fn: Fn, scope?: any): boolean { + return false; + } + + // @ts-ignore: arguments + removeListener(id: string, fn: Fn, scope?: any): boolean { + return false; + } + + // @ts-ignore: arguments + notify(event: Event) {} + + notifyChange(id: number) { + this.notify({ id: EVENT_CHANGED, target: this, value: id }); + } +} diff --git a/packages/ecs/src/component.ts b/packages/ecs/src/component.ts new file mode 100644 index 0000000000..083e63a05c --- /dev/null +++ b/packages/ecs/src/component.ts @@ -0,0 +1,171 @@ +import { + Event, + Fn, + IID, + INotify, + INotifyMixin, + UIntArray +} from "@thi.ng/api"; +import { isFunction } from "@thi.ng/checks"; +import { + ComponentDefaultValue, + EVENT_ADDED, + EVENT_CHANGED, + EVENT_PRE_DELETE, + IComponent, + ObjectComponentOpts +} from "./api"; + +@INotifyMixin +export class Component + implements IComponent, IID, INotify { + readonly id: K; + + sparse: UIntArray; + dense: UIntArray; + vals: T[]; + n: number; + + default?: ComponentDefaultValue; + owner?: IID; + + constructor( + sparse: UIntArray, + dense: UIntArray, + opts: ObjectComponentOpts + ) { + this.sparse = sparse; + this.dense = dense; + this.id = opts.id; + this.default = opts.default; + this.vals = new Array(this.dense.length); + this.n = 0; + } + + get size() { + return 1; + } + get stride() { + return 1; + } + + keys() { + return this.dense.slice(0, this.n); + } + + *values() { + for (let i = this.n; --i >= 0; ) { + yield this.getIndex(i)!; + } + } + + packedValues() { + return this.vals.slice(0, this.n); + } + + // TODO add version support via IDGen + add(id: number, val?: T) { + const { dense, sparse, n } = this; + const max = dense.length; + const i = sparse[id]; + if (id < max && n < max && !(i < n && dense[i] === id)) { + dense[n] = id; + sparse[id] = n; + const def = this.default; + const initVal = val || (isFunction(def) ? def() : def); + initVal && (this.vals[n] = initVal); + this.n++; + this.notify({ id: EVENT_ADDED, target: this, value: id }); + return true; + } + return false; + } + + delete(id: number) { + let { dense, sparse, vals, n } = this; + let i = sparse[id]; + if (i < n && dense[i] === id) { + // notify listeners prior to removal to allow restructure / swaps + this.notify({ id: EVENT_PRE_DELETE, target: this, value: id }); + // get possibly updated slot + i = sparse[id]; + const j = dense[--n]; + dense[i] = j; + sparse[j] = i; + this.n = n; + vals[i] = vals[n]; + delete vals[n]; + return true; + } + return false; + } + + has(id: number): boolean { + const i = this.sparse[id]; + return i < this.n && this.dense[i] === id; + } + + get(id: number) { + let i = this.sparse[id]; + return i < this.n && this.dense[i] === id ? this.vals[i] : undefined; + } + + getIndex(i: number) { + return i < this.n ? this.vals[i] : undefined; + } + + set(id: number, val: T) { + let i = this.sparse[id]; + if (i < this.n && this.dense[i] === id) { + this.vals[i] = val; + this.notifyChange(id); + return true; + } + return false; + } + + setIndex(i: number, val: T) { + return this.set(this.dense[i], val); + } + + /** + * Swaps slots of `src` & `dest` indices. The given args are NOT + * keys, but indices in the `dense` array. The corresponding sparse + * & value slots are swapped too. Returns true if swap happened + * (false, if `src` and `dest` are equal) + * + * @param src + * @param dest + */ + swapIndices(src: number, dest: number) { + if (src === dest) return false; + const { dense, sparse, vals } = this; + const ss = dense[src]; + const sd = dense[dest]; + dense[src] = sd; + dense[dest] = ss; + sparse[ss] = dest; + sparse[sd] = src; + const tmp = vals[src]; + vals[src] = vals[dest]; + vals[dest] = tmp; + return true; + } + + // @ts-ignore: arguments + addListener(id: string, fn: Fn, scope?: any): boolean { + return false; + } + + // @ts-ignore: arguments + removeListener(id: string, fn: Fn, scope?: any): boolean { + return false; + } + + // @ts-ignore: arguments + notify(event: Event) {} + + notifyChange(id: number) { + this.notify({ id: EVENT_CHANGED, target: this, value: id }); + } +} diff --git a/packages/ecs/src/ecs.ts b/packages/ecs/src/ecs.ts new file mode 100644 index 0000000000..6ac920fbc2 --- /dev/null +++ b/packages/ecs/src/ecs.ts @@ -0,0 +1,147 @@ +import { + assert, + Event, + Fn, + INotify, + INotifyMixin, + Type, + typedArray +} from "@thi.ng/api"; +import { isArray, isString } from "@thi.ng/checks"; +import { filter } from "@thi.ng/transducers"; +import { + ComponentID, + EVENT_ADDED, + EVENT_PRE_DELETE, + GroupOpts, + IComponent, + MemMappedComponentOpts, + ObjectComponentOpts +} from "./api"; +import { Component } from "./component"; +import { MemMappedComponent } from "./component-mm"; +import { Group } from "./group"; +import { IDGen } from "./id"; + +let NEXT_GROUP_ID = 0; + +@INotifyMixin +export class ECS implements INotify { + idgen: IDGen; + components: Map, IComponent, any, any>>; + groups: Map>; + + constructor(capacity = 1000) { + this.idgen = new IDGen(capacity, 0); + this.components = new Map(); + this.groups = new Map(); + } + + defEntity>( + comps?: K[] | IComponent[] | Partial> + ) { + const id = this.idgen.next(); + assert( + id !== undefined, + `max number of components reached (${this.idgen.capacity})` + ); + if (comps) { + if (isArray(comps)) { + if (!comps.length) return id!; + for (let cid of comps) { + const comp = isString(cid) ? this.components.get(cid) : cid; + assert(!!comp, `unknown component ID: ${cid}`); + comp!.add(id!); + } + } else { + for (let cid in comps) { + const comp = this.components.get(cid); + assert(!!comp, `unknown component ID: ${cid}`); + comp!.add(id!, comps[cid]); + } + } + } + this.notify({ id: EVENT_ADDED, target: this, value: id }); + return id!; + } + + defComponent>( + opts: MemMappedComponentOpts + ): MemMappedComponent; + defComponent>( + opts: ObjectComponentOpts + ): Component; + defComponent>(opts: any) { + assert( + !this.components.has(opts.id), + `component '${opts.id}' already existing` + ); + const cap = this.idgen.capacity; + const utype = uintType(cap); + const sparse = typedArray(utype, cap); + const dense = typedArray(utype, cap); + const comp: IComponent = + opts.type !== undefined + ? new MemMappedComponent(dense, sparse, opts) + : new Component(sparse, dense, opts); + this.components.set(opts.id, comp); + return comp; + } + + defGroup>( + comps: IComponent[], + owned: IComponent[] = comps, + opts: Partial = {} + ) { + opts = { + id: `group${NEXT_GROUP_ID++}`, + ...opts + }; + assert( + !this.groups.has(opts.id!), + `group '${opts.id}' already existing` + ); + const g = new Group(comps, owned, opts); + this.groups.set(g.id, g); + return g; + } + + hasID(id: number) { + this.idgen.isValid(id); + } + + deleteID(id: number) { + if (this.idgen.free(id)) { + this.notify({ id: EVENT_PRE_DELETE, target: this, value: id }); + for (let c of this.componentsForID(id)) { + c.delete(id); + } + return true; + } + return false; + } + + componentsForID(id: number) { + return filter((c) => c.has(id), this.components.values()); + } + + groupsForID(id: number) { + return filter((g) => g.has(id), this.groups.values()); + } + + // @ts-ignore: arguments + addListener(id: string, fn: Fn, scope?: any): boolean { + return false; + } + + // @ts-ignore: arguments + removeListener(id: string, fn: Fn, scope?: any): boolean { + return false; + } + + // @ts-ignore: arguments + notify(event: Event) {} +} + +const uintType = (num: number) => + num <= 0x100 ? Type.U8 : num <= 0x10000 ? Type.U16 : Type.U32; diff --git a/packages/ecs/src/group.ts b/packages/ecs/src/group.ts new file mode 100644 index 0000000000..075f880314 --- /dev/null +++ b/packages/ecs/src/group.ts @@ -0,0 +1,233 @@ +import { + assert, + Event, + FnO2, + FnO3, + IID +} from "@thi.ng/api"; +import { intersectionR } from "@thi.ng/associative"; +import { map, transduce } from "@thi.ng/transducers"; +import { + ComponentID, + EVENT_ADDED, + EVENT_CHANGED, + EVENT_PRE_DELETE, + GroupInfo, + GroupOpts, + GroupTuple, + ICache, + IComponent +} from "./api"; +import { Component } from "./component"; +import { UnboundedCache } from "./unbounded"; + +export class Group> implements IID { + readonly id: string; + + components: IComponent[]; + owned: IComponent[]; + ids: Set; + n: number; + + info: GroupInfo; + cache: ICache>; + + constructor( + comps: IComponent[], + owned: IComponent[] = comps, + opts: GroupOpts + ) { + this.components = comps; + this.ids = new Set(); + this.n = 0; + this.id = opts.id; + this.cache = opts.cache || new UnboundedCache(); + + this.info = comps.reduce( + (acc: GroupInfo, c) => { + acc[c.id] = { + values: c.vals, + size: c.size, + stride: c.stride + }; + return acc; + }, + {} + ); + + // update ownerships + owned.forEach((c) => { + assert( + comps.includes(c), + `owned component ${c.id} not in given list` + ); + assert( + !c.owner, + () => `component ${c.id} already owned by ${c.owner!.id}` + ); + c.owner = this; + }); + this.owned = owned; + this.addExisting(); + + comps.forEach((comp) => { + comp.addListener(EVENT_ADDED, this.onAddListener, this); + comp.addListener(EVENT_PRE_DELETE, this.onDeleteListener, this); + comp.addListener(EVENT_CHANGED, this.onChangeListener, this); + }); + } + + release() { + this.components.forEach((comp) => { + comp.removeListener(EVENT_ADDED, this.onAddListener, this); + comp.removeListener(EVENT_PRE_DELETE, this.onDeleteListener, this); + comp.removeListener(EVENT_CHANGED, this.onChangeListener, this); + }); + this.cache.release(); + } + + has(id: number) { + return this.ids.has(id); + } + + values() { + return this.isFullyOwning() + ? this.ownedValues() + : this.nonOwnedValues(); + } + + getIndex(i: number) { + this.ensureFullyOwning(); + return i < this.n + ? this.getEntityUnsafe(this.components[0].dense[i]) + : undefined; + } + + getEntity(id: number) { + return this.has(id) ? this.getEntityUnsafe(id) : undefined; + } + + getEntityUnsafe(id: number) { + return this.cache.getSet(id, () => { + const tuple = >{ id: id }; + const comps = this.components; + for (let j = comps.length; --j >= 0; ) { + const c = comps[j]; + tuple[c.id] = c.getIndex(c.sparse[id])!; + } + return tuple; + }); + } + + run(fn: FnO2, number, void>, ...xs: any[]) { + this.ensureFullyOwning(); + fn(this.info, this.n, ...xs); + } + + forEachRaw( + fn: FnO3, number, number, void>, + ...xs: any[] + ) { + this.ensureFullyOwning(); + const info = this.info; + const ref = this.components[0].dense; + for (let i = 0, n = this.n; i < n; i++) { + fn(info, ref[i], i, ...xs); + } + } + + forEach(fn: FnO2, number, void>, ...xs: any[]) { + let i = 0; + for (let id of this.ids) { + fn(this.getEntityUnsafe(id), i++, ...xs); + } + } + + isFullyOwning() { + return this.owned.length === this.components.length; + } + + isValidID(id: number) { + for (let comp of this.components) { + if (!comp.has(id)) return false; + } + return true; + } + + protected onAddListener(e: Event) { + // console.log(`add ${e.target.id}: ${e.value}`); + this.addID(e.value); + } + + protected onDeleteListener(e: Event) { + // console.log(`delete ${e.target.id}: ${e.value}`); + this.removeID(e.value); + } + + protected onChangeListener(e: Event) { + if (e.target instanceof Component) { + // console.log(`invalidate ${e.target.id}: ${e.value}`); + this.cache.delete(e.value); + } + } + + protected addExisting() { + const existing: Set = transduce( + map((c) => c.keys()), + intersectionR(), + this.components + ); + for (let id of existing) { + this.addID(id, false); + } + } + + protected addID(id: number, validate = true) { + if (validate && !this.isValidID(id)) return; + this.ids.add(id); + this.reorderOwned(id, this.n++); + } + + protected removeID(id: number, validate = true) { + if (validate && !this.isValidID(id)) return; + this.ids.delete(id); + this.reorderOwned(id, --this.n); + } + + protected reorderOwned(id: number, n: number) { + const owned = this.owned; + if (!owned.length) return; + const id2 = owned[0].dense[n]; + let swapped = false; + for (let i = owned.length; --i >= 0; ) { + const comp = owned[i]; + // console.log(`moving id: ${id} in ${comp.id}...`); + swapped = comp.swapIndices(comp.sparse[id], n) || swapped; + } + if (swapped) { + this.cache.delete(id); + this.cache.delete(id2); + } + } + + protected *ownedValues() { + const comps = this.components; + const ref = comps[0].dense; + for (let i = this.n; --i >= 0; ) { + yield this.getEntityUnsafe(ref[i]); + } + } + + protected *nonOwnedValues() { + for (let id of this.ids) { + yield this.getEntityUnsafe(id); + } + } + + protected ensureFullyOwning() { + assert( + this.isFullyOwning(), + `group ${this.id} isn't fully owning its components` + ); + } +} diff --git a/packages/ecs/src/id.ts b/packages/ecs/src/id.ts new file mode 100644 index 0000000000..0dffab86d8 --- /dev/null +++ b/packages/ecs/src/id.ts @@ -0,0 +1,88 @@ +import { assert } from "@thi.ng/api"; + +export class IDGen { + ids: number[]; + nextID: number; + capacity: number; + + constructor(cap = Infinity, next = 0) { + this.ids = []; + this.capacity = cap; + this.nextID = next; + } + + next() { + return this.ids.length + ? this.ids.pop()! + : this.nextID < this.capacity + ? this.nextID++ + : undefined; + } + + free(id: number) { + if (this.isValid(id)) { + this.ids.push(id); + return true; + } + return false; + } + + isValid(id: number) { + return id < this.nextID && !this.ids.includes(id); + } + + numUsed() { + return this.nextID - this.ids.length; + } + + numAvailable() { + return this.capacity - this.numUsed(); + } +} + +export class VersionedIdGen extends IDGen { + mask: number; + shift: number; + + constructor(bits: number, cap = (1 << bits) >>> 0, next = 0) { + const maxCap = (1 << bits) >>> 0; + assert(cap <= maxCap, "capacity too large for given bit size"); + super(cap, next); + this.mask = maxCap - 1; + this.shift = bits; + } + + id(id: number) { + return id & this.mask; + } + + version(id: number) { + return id >>> this.shift; + } + + next() { + if (this.ids.length) { + const id = this.ids.pop()!; + return ( + ((id & this.mask) | ((id & ~this.mask) + (1 << this.shift))) >>> + 0 + ); + } + assert(this.nextID < this.capacity, "max capacity reached"); + return this.nextID++; + } + + free(id: number) { + // FIXME use this.isValid() + if ((id & this.mask) < this.nextID) { + this.ids.push(id); + return true; + } + return false; + } + + isValid(id: number) { + // FIXME need to check/iterate ids manually & apply mask + return super.isValid(id & this.mask); + } +} diff --git a/packages/ecs/src/index.ts b/packages/ecs/src/index.ts new file mode 100644 index 0000000000..979622272c --- /dev/null +++ b/packages/ecs/src/index.ts @@ -0,0 +1,9 @@ +export * from "./api"; +export * from "./component"; +export * from "./component-mm"; +export * from "./ecs"; +export * from "./group"; +export * from "./id"; +export * from "./lru"; +export * from "./null"; +export * from "./unbounded"; diff --git a/packages/ecs/src/lru.ts b/packages/ecs/src/lru.ts new file mode 100644 index 0000000000..b17021a1db --- /dev/null +++ b/packages/ecs/src/lru.ts @@ -0,0 +1,66 @@ +import { Fn0 } from "@thi.ng/api"; +import { ConsCell, DCons } from "@thi.ng/dcons"; +import { ICache } from "./api"; + +type LRUEntry = { k: number; v: T }; + +export class LRU implements ICache { + items: DCons>; + index: Map>>; + capacity: number; + + constructor(cap: number) { + this.items = new DCons(); + this.index = new Map(); + this.capacity = cap; + } + + keys() { + return this.index.keys(); + } + + release() { + this.items.release(); + this.index.clear(); + return true; + } + + set(key: number, val: T) { + const { items, index } = this; + let node = index.get(key); + if (node) { + node.value.v = val; + items.asHead(node); + } else { + items.cons({ k: key, v: val }); + index.set(key, items.head!); + while (items.length > this.capacity) { + this.index.delete(this.items.pop()!.k); + } + } + return val; + } + + get(key: number) { + const node = this.index.get(key); + if (node) { + this.items.asHead(node); + return node.value.v; + } + } + + getSet(key: number, notFound: Fn0) { + let val = this.get(key); + return val !== undefined ? val : this.set(key, notFound()); + } + + delete(key: number) { + const node = this.index.get(key); + if (node) { + this.index.delete(key); + this.items.remove(node); + return true; + } + return false; + } +} diff --git a/packages/ecs/src/null.ts b/packages/ecs/src/null.ts new file mode 100644 index 0000000000..f7abcdd13b --- /dev/null +++ b/packages/ecs/src/null.ts @@ -0,0 +1,26 @@ +import { Fn0 } from "@thi.ng/api"; +import { ICache } from "./api"; + +export class NullCache implements ICache { + release() { + return true; + } + + *keys() {} + + set(_: number, val: T): T { + return val; + } + + get(_: number): T | undefined { + return; + } + + getSet(_: number, notFound: Fn0): T { + return notFound(); + } + + delete(_: number): boolean { + return true; + } +} diff --git a/packages/ecs/src/unbounded.ts b/packages/ecs/src/unbounded.ts new file mode 100644 index 0000000000..f308187248 --- /dev/null +++ b/packages/ecs/src/unbounded.ts @@ -0,0 +1,37 @@ +import { Fn0 } from "@thi.ng/api"; +import { ICache } from "./api"; + +export class UnboundedCache implements ICache { + index: Map; + + constructor() { + this.index = new Map(); + } + + release() { + this.index.clear(); + return true; + } + + keys() { + return this.index.keys(); + } + + set(key: number, val: T): T { + this.index.set(key, val); + return val; + } + + get(key: number): T | undefined { + return this.index.get(key); + } + + getSet(key: number, notFound: Fn0): T { + let val = this.index.get(key); + return val !== undefined ? val : this.set(key, notFound()); + } + + delete(key: number): boolean { + return this.index.delete(key); + } +} diff --git a/packages/ecs/test/component.ts b/packages/ecs/test/component.ts new file mode 100644 index 0000000000..040bd3baa9 --- /dev/null +++ b/packages/ecs/test/component.ts @@ -0,0 +1,79 @@ +import { Type } from "@thi.ng/api"; +import { equiv } from "@thi.ng/equiv"; +import * as assert from "assert"; +import { ECS, MemMappedComponent } from "../src"; + +describe("component", () => { + let ecs: ECS; + + beforeEach(() => (ecs = new ECS(16))); + + it("defComponent (minimal)", () => { + const a = ecs.defComponent({ id: "a", type: Type.F32 }); + assert(a instanceof MemMappedComponent); + assert(a.dense instanceof Uint8Array); + assert(a.sparse instanceof Uint8Array); + assert(a.vals instanceof Float32Array); + assert.equal(a.dense.length, ecs.idgen.capacity); + assert.equal(a.sparse.length, ecs.idgen.capacity); + assert.equal(a.vals.length, ecs.idgen.capacity); + assert.equal(a.size, 1); + assert.equal(a.stride, 1); + }); + + it("defComponent (w/ type)", () => { + const a = ecs.defComponent({ id: "a", type: Type.U8 }); + assert(a.vals instanceof Uint8Array); + assert.equal(a.dense.length, ecs.idgen.capacity); + assert.equal(a.sparse.length, ecs.idgen.capacity); + assert.equal(a.vals.length, ecs.idgen.capacity); + assert.equal(a.size, 1); + assert.equal(a.stride, 1); + }); + + it("defComponent (w/ size)", () => { + const a = ecs.defComponent({ id: "a", type: Type.F32, size: 2 }); + assert(a.vals instanceof Float32Array); + assert.equal(a.vals.length, ecs.idgen.capacity * 2); + assert.equal(a.size, 2); + assert.equal(a.stride, 2); + const b = ecs.defComponent({ + id: "b", + type: Type.F32, + size: 3, + stride: 4 + }); + assert.equal(b.vals.length, ecs.idgen.capacity * 4); + assert.equal(b.size, 3); + assert.equal(b.stride, 4); + }); + + it("add (w/ default val)", () => { + const a = ecs.defComponent({ + id: "a", + type: Type.F32, + size: 2, + default: [1, 2] + }); + assert(a.add(8)); + assert(a.add(9, [10, 20])); + assert(!a.add(16)); + assert.deepEqual([...a.get(8)!], [1, 2]); + assert.deepEqual([...a.get(9)!], [10, 20]); + assert(!a.add(8, [-1, -2])); + assert.deepEqual([...a.get(8)!], [1, 2]); + }); + + it("values / packeValues", () => { + const a = ecs.defComponent({ + id: "a", + type: Type.F32, + size: 2, + default: [1, 2] + }); + assert(a.add(8)); + assert(a.add(9, [10, 20])); + assert.deepEqual([...a.packedValues()], [1, 2, 10, 20]); + assert(equiv([...a.values()], [[10, 20], [1, 2]])); + }); +}); diff --git a/packages/ecs/test/group.ts b/packages/ecs/test/group.ts new file mode 100644 index 0000000000..887e28aa9a --- /dev/null +++ b/packages/ecs/test/group.ts @@ -0,0 +1,49 @@ +import { equiv } from "@thi.ng/equiv"; +import * as assert from "assert"; +import { ECS, Group } from "../src"; + +const collect = (g: Group) => { + let res: any[] = []; + g.forEach((x) => res.push(x)); + return res; +}; + +describe("component", () => { + let ecs: ECS; + + beforeEach(() => (ecs = new ECS(16))); + + it("group", () => { + const a = ecs.defComponent({ id: "a", default: () => "a" }); + const b = ecs.defComponent({ id: "b", type: 7, size: 2 }); + const g = ecs.defGroup([a, b]); + ecs.defEntity(["a", "b"]); + ecs.defEntity({ a: "aa", b: [1, 2] }); + ecs.defEntity({ a: "aaa", b: [3, 4] }); + assert.ok(g.has(0)); + assert.ok(g.has(1)); + assert.ok(g.has(2)); + assert.ok(!g.has(3)); + assert.deepEqual([...ecs.componentsForID(2)], [a, b]); + assert.deepEqual([...ecs.groupsForID(2)], [g]); + assert.ok( + equiv(collect(g), [ + { a: "a", b: [0, 0], id: 0 }, + { a: "aa", b: [1, 2], id: 1 }, + { a: "aaa", b: [3, 4], id: 2 } + ]) + ); + + a.delete(0); + assert.ok( + equiv(collect(g), [ + { a: "aa", b: [1, 2], id: 1 }, + { a: "aaa", b: [3, 4], id: 2 } + ]) + ); + a.delete(2); + assert.ok(equiv(collect(g), [{ a: "aa", b: [1, 2], id: 1 }])); + a.set(1, "hi"); + assert.ok(equiv(collect(g), [{ a: "hi", b: [1, 2], id: 1 }])); + }); +}); diff --git a/packages/ecs/test/tsconfig.json b/packages/ecs/test/tsconfig.json new file mode 100644 index 0000000000..f6e63560dd --- /dev/null +++ b/packages/ecs/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build", + "module": "commonjs" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/ecs/tsconfig.json b/packages/ecs/tsconfig.json new file mode 100644 index 0000000000..893b9979c5 --- /dev/null +++ b/packages/ecs/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "module": "es6", + "target": "es6" + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/equiv/CHANGELOG.md b/packages/equiv/CHANGELOG.md index b13e45a7a0..1d099af086 100644 --- a/packages/equiv/CHANGELOG.md +++ b/packages/equiv/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/equiv@1.0.9...@thi.ng/equiv@1.0.10) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/equiv + + + + + ## [1.0.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/equiv@1.0.8...@thi.ng/equiv@1.0.9) (2019-07-31) **Note:** Version bump only for package @thi.ng/equiv diff --git a/packages/equiv/package.json b/packages/equiv/package.json index ceba521f48..9b843aa88b 100644 --- a/packages/equiv/package.json +++ b/packages/equiv/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/equiv", - "version": "1.0.9", + "version": "1.0.10", "description": "Extensible deep equivalence checking for any data types", "module": "./index.js", "main": "./lib/index.js", @@ -30,8 +30,8 @@ "benchmark": "^2.1.4", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "keywords": [ "deep", diff --git a/packages/errors/CHANGELOG.md b/packages/errors/CHANGELOG.md index 36bb6cf65a..9103a0506c 100644 --- a/packages/errors/CHANGELOG.md +++ b/packages/errors/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/errors@1.2.0...@thi.ng/errors@1.2.1) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/errors + + + + + +# [1.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/errors@1.1.2...@thi.ng/errors@1.2.0) (2019-08-21) + + +### Features + +* **errors:** add defError(), refactor all existing, update readme ([ded89c2](https://github.com/thi-ng/umbrella/commit/ded89c2)) + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/errors@1.1.1...@thi.ng/errors@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/errors diff --git a/packages/errors/README.md b/packages/errors/README.md index b93e0f4ba1..49fd210bea 100644 --- a/packages/errors/README.md +++ b/packages/errors/README.md @@ -9,11 +9,11 @@ This project is part of the ## About -Custom error types and helper fns used by many packages in this repo. +Custom error types (extending `Error`) and helper functions used by most +other packages in this repo. -This feature was previously part of the -[@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) -package. +Additional error types can be defined using +[`defError()`](https://github.com/thi-ng/umbrella/tree/master/packages/errors/src/deferror.ts) ## Installation @@ -41,6 +41,21 @@ err.illegalState("oops"); err.unsupported("TODO not yet implemented") // Error: unsupported operation: TODO not yet implemented + +// define custom error +const MyError = err.defError( + () => "Eeek... ", + (x) => x + " is not allowed!" +); + +try { + throw new MyError(23); +} catch(e) { + console.warn(e.message); + console.log(e instanceof Error); +} +// Eeek... 23 is not allowed! +// true ``` ## Authors diff --git a/packages/errors/package.json b/packages/errors/package.json index 35e9e8b0d5..a02e878b84 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/errors", - "version": "1.1.2", + "version": "1.2.1", "description": "Custom error types and helper fns.", "module": "./index.js", "main": "./lib/index.js", @@ -29,8 +29,8 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "keywords": [ "error", diff --git a/packages/errors/src/deferror.ts b/packages/errors/src/deferror.ts new file mode 100644 index 0000000000..a910e73c44 --- /dev/null +++ b/packages/errors/src/deferror.ts @@ -0,0 +1,9 @@ +export const defError = ( + prefix: (msg?: T) => string, + suffix: (msg?: T) => string = (msg) => (msg !== undefined ? ": " + msg : "") +) => + class extends Error { + constructor(msg?: T) { + super(prefix(msg) + suffix(msg)); + } + }; diff --git a/packages/errors/src/illegal-arguments.ts b/packages/errors/src/illegal-arguments.ts index c2807673ac..b803783c50 100644 --- a/packages/errors/src/illegal-arguments.ts +++ b/packages/errors/src/illegal-arguments.ts @@ -1,8 +1,6 @@ -export class IllegalArgumentError extends Error { - constructor(msg?: any) { - super("illegal argument(s)" + (msg !== undefined ? ": " + msg : "")); - } -} +import { defError } from "./deferror"; + +export const IllegalArgumentError = defError(() => "illegal argument(s)"); export const illegalArgs = (msg?: any): never => { throw new IllegalArgumentError(msg); diff --git a/packages/errors/src/illegal-arity.ts b/packages/errors/src/illegal-arity.ts index 3d3d18173d..f4482aaa33 100644 --- a/packages/errors/src/illegal-arity.ts +++ b/packages/errors/src/illegal-arity.ts @@ -1,8 +1,6 @@ -export class IllegalArityError extends Error { - constructor(n: number) { - super(`illegal arity: ${n}`); - } -} +import { defError } from "./deferror"; + +export const IllegalArityError = defError(() => "illegal arity"); export const illegalArity = (n: number): never => { throw new IllegalArityError(n); diff --git a/packages/errors/src/illegal-state.ts b/packages/errors/src/illegal-state.ts index a0aeb8ad40..d5aa166e51 100644 --- a/packages/errors/src/illegal-state.ts +++ b/packages/errors/src/illegal-state.ts @@ -1,8 +1,6 @@ -export class IllegalStateError extends Error { - constructor(msg?: any) { - super("illegal state" + (msg !== undefined ? ": " + msg : "")); - } -} +import { defError } from "./deferror"; + +export const IllegalStateError = defError(() => "illegal state"); export const illegalState = (msg?: any): never => { throw new IllegalStateError(msg); diff --git a/packages/errors/src/index.ts b/packages/errors/src/index.ts index f4fff5e84e..ac23228467 100644 --- a/packages/errors/src/index.ts +++ b/packages/errors/src/index.ts @@ -1,3 +1,4 @@ +export * from "./deferror"; export * from "./illegal-arguments"; export * from "./illegal-arity"; export * from "./illegal-state"; diff --git a/packages/errors/src/unsupported.ts b/packages/errors/src/unsupported.ts index 88face590c..0f4b6349de 100644 --- a/packages/errors/src/unsupported.ts +++ b/packages/errors/src/unsupported.ts @@ -1,8 +1,8 @@ -export class UnsupportedOperationError extends Error { - constructor(msg?: any) { - super("unsupported operation" + (msg !== undefined ? ": " + msg : "")); - } -} +import { defError } from "./deferror"; + +export const UnsupportedOperationError = defError( + () => "unsupported operation" +); export const unsupported = (msg?: any): never => { throw new UnsupportedOperationError(msg); diff --git a/packages/fsm/CHANGELOG.md b/packages/fsm/CHANGELOG.md index bfbc6063c2..ed1edced0f 100644 --- a/packages/fsm/CHANGELOG.md +++ b/packages/fsm/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/fsm@2.2.5...@thi.ng/fsm@2.3.0) (2019-11-09) + + +### Features + +* **fsm:** update str() to NOT collect input by default (match only) ([6105ea7](https://github.com/thi-ng/umbrella/commit/6105ea7f8a9c99b0117bb6db2396607438c1eb02)) + + + + + +## [2.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/fsm@2.2.4...@thi.ng/fsm@2.2.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/fsm + + + + + +## [2.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/fsm@2.2.3...@thi.ng/fsm@2.2.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/fsm + + + + + +## [2.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/fsm@2.2.2...@thi.ng/fsm@2.2.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/fsm + + + + + ## [2.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/fsm@2.2.1...@thi.ng/fsm@2.2.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/fsm diff --git a/packages/fsm/package.json b/packages/fsm/package.json index 43a3313d34..334c6c8a37 100644 --- a/packages/fsm/package.json +++ b/packages/fsm/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/fsm", - "version": "2.2.2", + "version": "2.3.0", "description": "Composable primitives for building declarative, transducer based Finite-State machines & parsers for arbitrary data streams", "module": "./index.js", "main": "./lib/index.js", @@ -29,15 +29,15 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/arrays": "^0.2.2", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/arrays": "^0.3.0", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "ES6", diff --git a/packages/fsm/src/fsm.ts b/packages/fsm/src/fsm.ts index f96a12ab53..9b7e79fe80 100644 --- a/packages/fsm/src/fsm.ts +++ b/packages/fsm/src/fsm.ts @@ -5,6 +5,7 @@ import { isReduced, iterator, Reducer, + ReductionFn, Transducer, unreduced } from "@thi.ng/transducers"; @@ -87,12 +88,7 @@ export function fsm( ); } if (res) { - for (let y of unreduced(res)) { - acc = reduce(acc, y); - if (isReduced(acc)) { - break; - } - } + acc = reduceResult(reduce, acc, res); isReduced(res) && (acc = ensureReduced(acc)); } if (type === Match.FULL_NC && !isReduced(acc)) { @@ -100,12 +96,7 @@ export function fsm( } } else if (type === Match.FAIL) { if (res) { - for (let y of unreduced(res)) { - acc = reduce(acc, y); - if (isReduced(acc)) { - break; - } - } + acc = reduceResult(reduce, acc, res); } return ensureReduced(acc); } @@ -116,3 +107,13 @@ export function fsm( ]; }; } + +const reduceResult = (rfn: ReductionFn, acc: any, res: R[]) => { + for (let x of unreduced(res)) { + acc = rfn(acc, x); + if (isReduced(acc)) { + break; + } + } + return acc; +}; diff --git a/packages/fsm/src/str.ts b/packages/fsm/src/str.ts index 5c9e340b19..9a3020f1ae 100644 --- a/packages/fsm/src/str.ts +++ b/packages/fsm/src/str.ts @@ -1,24 +1,49 @@ -import { LitCallback, Match, Matcher, RES_PARTIAL } from "./api"; +import { + LitCallback, + Match, + Matcher, + RES_PARTIAL +} from "./api"; import { result } from "./result"; /** * String-only version of `seq()`. Returns `Match.FULL` once the entire - * given string could be matched. + * given string could be matched. Unless `collect` is true (default: + * false), only matches given string and does not collect input. + * Therefore then also only passes an empty string to `fail` callback. + * If `collect` is true, the failed callback will be called with the + * collected input. * * @param str * @param success * @param fail + * @param collect */ export const str = ( str: string, success?: LitCallback, - fail?: LitCallback -): Matcher => () => { - let buf = ""; - return (ctx, x) => - (buf += x) === str - ? result(success && success(ctx, buf)) - : str.indexOf(buf) === 0 - ? RES_PARTIAL - : result(fail && fail(ctx, buf), Match.FAIL); -}; + fail?: LitCallback, + collect = false +): Matcher => + collect + ? () => { + let buf = ""; + return (ctx, x) => + (buf += x) === str + ? result(success && success(ctx, buf)) + : str.indexOf(buf) === 0 + ? RES_PARTIAL + : result(fail && fail(ctx, buf), Match.FAIL); + } + : () => { + let matched = 0; + let i = 0; + return (ctx, x) => { + str.charAt(i++) === x && matched++; + return matched === str.length + ? result(success && success(ctx, str)) + : matched === i + ? RES_PARTIAL + : result(fail && fail(ctx, ""), Match.FAIL); + }; + }; diff --git a/packages/geom-accel/CHANGELOG.md b/packages/geom-accel/CHANGELOG.md index 2c623a6371..2ecf2b6af1 100644 --- a/packages/geom-accel/CHANGELOG.md +++ b/packages/geom-accel/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.2.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-accel@1.2.8...@thi.ng/geom-accel@1.2.9) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-accel + + + + + +## [1.2.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-accel@1.2.7...@thi.ng/geom-accel@1.2.8) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-accel + + + + + +## [1.2.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-accel@1.2.6...@thi.ng/geom-accel@1.2.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-accel + + + + + +## [1.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-accel@1.2.5...@thi.ng/geom-accel@1.2.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-accel + + + + + +## [1.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-accel@1.2.4...@thi.ng/geom-accel@1.2.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-accel + + + + + +## [1.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-accel@1.2.3...@thi.ng/geom-accel@1.2.4) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-accel + + + + + ## [1.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-accel@1.2.2...@thi.ng/geom-accel@1.2.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-accel diff --git a/packages/geom-accel/package.json b/packages/geom-accel/package.json index 83409df93a..b6b6576f00 100644 --- a/packages/geom-accel/package.json +++ b/packages/geom-accel/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-accel", - "version": "1.2.3", + "version": "1.2.9", "description": "nD spatial indexing data structures", "module": "./index.js", "main": "./lib/index.js", @@ -29,17 +29,17 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/arrays": "^0.2.2", - "@thi.ng/geom-api": "^0.3.1", - "@thi.ng/heaps": "^1.1.2", - "@thi.ng/math": "^1.4.2", - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/api": "^6.5.0", + "@thi.ng/arrays": "^0.3.0", + "@thi.ng/geom-api": "^0.3.7", + "@thi.ng/heaps": "^1.1.5", + "@thi.ng/math": "^1.5.0", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/geom-accel/src/kdtree.ts b/packages/geom-accel/src/kdtree.ts index f79e45378e..b75793dd0b 100644 --- a/packages/geom-accel/src/kdtree.ts +++ b/packages/geom-accel/src/kdtree.ts @@ -1,4 +1,4 @@ -import { ICopy, Pair } from "@thi.ng/api"; +import { Fn, ICopy, Pair } from "@thi.ng/api"; import { ensureArray } from "@thi.ng/arrays"; import { ISpatialAccel } from "@thi.ng/geom-api"; import { Heap } from "@thi.ng/heaps"; @@ -102,7 +102,7 @@ export class KdTree : parent; let parent: MaybeKdNode; if (this.root) { - parent = nearest1(p, [eps * eps, null], [], this.dim, this.root)[1]; + parent = nearest1(p, [eps * eps, null], this.dim, this.root)[1]; if (parent) { return false; } @@ -154,74 +154,20 @@ export class KdTree has(k: Readonly, eps = EPS) { return ( !!this.root && - !!nearest1(k, [eps * eps, null], [], this.dim, this.root)[1] + !!nearest1(k, [eps * eps, null], this.dim, this.root)[1] ); } select(q: Readonly, maxNum: number, maxDist?: number): Pair[] { - if (!this.root) return []; - const res: Pair[] = []; - if (maxNum === 1) { - const sel = nearest1( - q, - [maxDist != null ? maxDist * maxDist : Infinity, null], - [], - this.dim, - this.root - )[1]; - sel && res.push([sel.k, sel.v]); - } else { - const sel = this.buildSelection(q, maxNum, maxDist); - for (let n = sel.length; --n >= 0; ) { - const nn = sel[n][1]; - nn && res.push([nn.k, nn.v]); - } - } - return res; + return this.doSelect(q, (x) => [x.k, x.v], maxNum, maxDist); } - selectKeys(q: Readonly, maxNum: number, maxDist?: number): K[] { - if (!this.root) return []; - const res: K[] = []; - if (maxNum === 1) { - const sel = nearest1( - q, - [maxDist != null ? maxDist * maxDist : Infinity, null], - [], - this.dim, - this.root - )[1]; - sel && res.push(sel.k); - } else { - const src = this.buildSelection(q, maxNum, maxDist); - for (let n = src.length; --n >= 0; ) { - const nn = src[n][1]; - nn && res.push(nn.k); - } - } - return res; + selectKeys(q: Readonly, maxNum: number, maxDist?: number) { + return this.doSelect(q, (x) => x.k, maxNum, maxDist); } - selectVals(q: Readonly, maxNum: number, maxDist?: number): V[] { - if (!this.root) return []; - const res: V[] = []; - if (maxNum === 1) { - const sel = nearest1( - q, - [maxDist != null ? maxDist * maxDist : Infinity, null], - [], - this.dim, - this.root - )[1]; - sel && res.push(sel.v); - } else { - const src = this.buildSelection(q, maxNum, maxDist); - for (let n = src.length; --n >= 0; ) { - const nn = src[n][1]; - nn && res.push(nn.v); - } - } - return res; + selectVals(q: Readonly, maxNum: number, maxDist?: number) { + return this.doSelect(q, (x) => x.v, maxNum, maxDist); } balanceRatio() { @@ -243,10 +189,36 @@ export class KdTree for (let i = maxNum; --i >= 0; ) { nodes.push(c); } - nearest(q, nodes, [], this.dim, maxNum, this.root!); + nearest(q, nodes, this.dim, maxNum, this.root!); return nodes.values.sort(CMP); } + protected doSelect( + q: Readonly, + f: Fn, T>, + maxNum: number, + maxDist?: number + ): T[] { + if (!this.root) return []; + const res: any[] = []; + if (maxNum === 1) { + const sel = nearest1( + q, + [maxDist != null ? maxDist * maxDist : Infinity, null], + this.dim, + this.root + )[1]; + sel && res.push(f(sel)); + } else { + const sel = this.buildSelection(q, maxNum, maxDist); + for (let n = sel.length; --n >= 0; ) { + const s = sel[n][1]; + s && res.push(f(s)); + } + } + return res; + } + protected buildTree( points: Pair[], depth: number, @@ -344,7 +316,6 @@ const remove = (node: KdNode) => { const nearest = ( q: K, acc: Heap<[number, MaybeKdNode]>, - tmp: Vec, dims: number, maxNum: number, node: KdNode @@ -352,38 +323,16 @@ const nearest = ( const p = node.k; const ndist = distSq(p, q); if (!node.l && !node.r) { - if (!acc.length || ndist < acc.peek()[0]) { - if (acc.length >= maxNum) { - acc.pushPop([ndist, node]); - } else { - acc.push([ndist, node]); - } - } + collect(acc, node, maxNum, ndist); return; } - const ndim = node.d; - for (let i = dims; --i >= 0; ) { - tmp[i] = i === ndim ? q[i] : p[i]; - } - const tdist = distSq(p, tmp); - let best = !node.r - ? node.l - : !node.l - ? node.r - : q[ndim] < p[ndim] - ? node.l - : node.r; - nearest(q, acc, tmp, dims, maxNum, best!); - if (!acc.length || ndist < acc.peek()[0]) { - if (acc.length >= maxNum) { - acc.pushPop([ndist, node]); - } else { - acc.push([ndist, node]); - } - } + const tdist = nodeDist(node, dims, q, p); + let best = bestChild(node, q); + nearest(q, acc, dims, maxNum, best!); + collect(acc, node, maxNum, ndist); if (!acc.length || tdist < acc.peek()[0]) { best = best === node.l ? node.r : node.l; - best && nearest(q, acc, tmp, dims, maxNum, best); + best && nearest(q, acc, dims, maxNum, best); } }; @@ -398,39 +347,64 @@ const nearest = ( const nearest1 = ( q: K, acc: [number, MaybeKdNode], - tmp: Vec, dims: number, node: KdNode ): [number, MaybeKdNode] => { const p = node.k; const ndist = distSq(p, q); if (!node.l && !node.r) { - if (ndist < acc[0]) { - acc[0] = ndist; - acc[1] = node; - } + collect1(acc, node, ndist); return acc; } - const ndim = node.d; - for (let i = dims; --i >= 0; ) { - tmp[i] = i === ndim ? q[i] : p[i]; + const tdist = nodeDist(node, dims, q, p); + let best = bestChild(node, q); + nearest1(q, acc, dims, best!); + collect1(acc, node, ndist); + if (tdist < acc[0]) { + best = best === node.l ? node.r : node.l; + best && nearest1(q, acc, dims, best); } - const tdist = distSq(p, tmp); - let best = !node.r + return acc; +}; + +const bestChild = (node: KdNode, q: K) => { + const d = node.d; + return !node.r ? node.l : !node.l ? node.r - : q[ndim] < p[ndim] + : q[d] < node.k[d] ? node.l : node.r; - nearest1(q, acc, tmp, dims, best!); - if (ndist < acc[0]) { - acc[0] = ndist; - acc[1] = node; - } - if (tdist < acc[0]) { - best = best === node.l ? node.r : node.l; - best && nearest1(q, acc, tmp, dims, best); +}; + +const collect = ( + acc: Heap<[number, MaybeKdNode]>, + node: KdNode, + maxNum: number, + ndist: number +) => + (!acc.length || ndist < acc.peek()[0]) && + (acc.length >= maxNum + ? acc.pushPop([ndist, node]) + : acc.push([ndist, node])); + +const collect1 = ( + acc: [number, MaybeKdNode], + node: KdNode, + ndist: number +) => ndist < acc[0] && ((acc[0] = ndist), (acc[1] = node)); + +const TMP: Vec = []; + +const nodeDist = ( + node: KdNode, + dims: number, + q: K, + p: K +) => { + for (let i = dims, d = node.d; --i >= 0; ) { + TMP[i] = i === d ? q[i] : p[i]; } - return acc; + return distSq(TMP, p); }; diff --git a/packages/geom-api/CHANGELOG.md b/packages/geom-api/CHANGELOG.md index 1be10130da..49001c3fd1 100644 --- a/packages/geom-api/CHANGELOG.md +++ b/packages/geom-api/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-api@0.3.6...@thi.ng/geom-api@0.3.7) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-api + + + + + +## [0.3.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-api@0.3.5...@thi.ng/geom-api@0.3.6) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-api + + + + + +## [0.3.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-api@0.3.4...@thi.ng/geom-api@0.3.5) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-api + + + + + +## [0.3.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-api@0.3.3...@thi.ng/geom-api@0.3.4) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-api + + + + + +## [0.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-api@0.3.2...@thi.ng/geom-api@0.3.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-api + + + + + +## [0.3.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-api@0.3.1...@thi.ng/geom-api@0.3.2) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-api + + + + + ## [0.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-api@0.3.0...@thi.ng/geom-api@0.3.1) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-api diff --git a/packages/geom-api/package.json b/packages/geom-api/package.json index 3e56a63148..dab6e94114 100644 --- a/packages/geom-api/package.json +++ b/packages/geom-api/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-api", - "version": "0.3.1", + "version": "0.3.7", "description": "Shared type & interface declarations for @thi.ng/geom packages", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/api": "^6.5.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "ES6", diff --git a/packages/geom-arc/CHANGELOG.md b/packages/geom-arc/CHANGELOG.md index a0f9a6ab82..1c734892a4 100644 --- a/packages/geom-arc/CHANGELOG.md +++ b/packages/geom-arc/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-arc@0.2.8...@thi.ng/geom-arc@0.2.9) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-arc + + + + + +## [0.2.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-arc@0.2.7...@thi.ng/geom-arc@0.2.8) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-arc + + + + + +## [0.2.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-arc@0.2.6...@thi.ng/geom-arc@0.2.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-arc + + + + + +## [0.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-arc@0.2.5...@thi.ng/geom-arc@0.2.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-arc + + + + + +## [0.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-arc@0.2.4...@thi.ng/geom-arc@0.2.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-arc + + + + + +## [0.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-arc@0.2.3...@thi.ng/geom-arc@0.2.4) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-arc + + + + + ## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-arc@0.2.2...@thi.ng/geom-arc@0.2.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-arc diff --git a/packages/geom-arc/package.json b/packages/geom-arc/package.json index 60d1df4b8c..5bc7a70719 100644 --- a/packages/geom-arc/package.json +++ b/packages/geom-arc/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-arc", - "version": "0.2.3", + "version": "0.2.9", "description": "2D circular / elliptic arc operations", "module": "./index.js", "main": "./lib/index.js", @@ -29,15 +29,15 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/checks": "^2.2.2", - "@thi.ng/geom-api": "^0.3.1", - "@thi.ng/geom-resample": "^0.2.3", - "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/checks": "^2.4.1", + "@thi.ng/geom-api": "^0.3.7", + "@thi.ng/geom-resample": "^0.2.9", + "@thi.ng/math": "^1.5.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/geom-clip/CHANGELOG.md b/packages/geom-clip/CHANGELOG.md index debbd06969..c58f954657 100644 --- a/packages/geom-clip/CHANGELOG.md +++ b/packages/geom-clip/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-clip@0.1.8...@thi.ng/geom-clip@0.1.9) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-clip + + + + + +## [0.1.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-clip@0.1.7...@thi.ng/geom-clip@0.1.8) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-clip + + + + + +## [0.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-clip@0.1.6...@thi.ng/geom-clip@0.1.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-clip + + + + + +## [0.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-clip@0.1.5...@thi.ng/geom-clip@0.1.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-clip + + + + + +## [0.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-clip@0.1.4...@thi.ng/geom-clip@0.1.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-clip + + + + + +## [0.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-clip@0.1.3...@thi.ng/geom-clip@0.1.4) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-clip + + + + + ## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-clip@0.1.2...@thi.ng/geom-clip@0.1.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-clip diff --git a/packages/geom-clip/package.json b/packages/geom-clip/package.json index 4538cf8ddb..5fb758a202 100644 --- a/packages/geom-clip/package.json +++ b/packages/geom-clip/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-clip", - "version": "0.1.3", + "version": "0.1.9", "description": "2D line & convex polygon clipping (Liang-Barsky / Sutherland-Hodgeman)", "module": "./index.js", "main": "./lib/index.js", @@ -29,14 +29,14 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/geom-isec": "^0.3.3", - "@thi.ng/geom-poly-utils": "^0.1.21", - "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/geom-isec": "^0.3.9", + "@thi.ng/geom-poly-utils": "^0.1.27", + "@thi.ng/math": "^1.5.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/geom-closest-point/CHANGELOG.md b/packages/geom-closest-point/CHANGELOG.md index 872dfc68ce..3fc85b96cb 100644 --- a/packages/geom-closest-point/CHANGELOG.md +++ b/packages/geom-closest-point/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-closest-point@0.3.8...@thi.ng/geom-closest-point@0.3.9) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-closest-point + + + + + +## [0.3.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-closest-point@0.3.7...@thi.ng/geom-closest-point@0.3.8) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-closest-point + + + + + +## [0.3.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-closest-point@0.3.6...@thi.ng/geom-closest-point@0.3.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-closest-point + + + + + +## [0.3.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-closest-point@0.3.5...@thi.ng/geom-closest-point@0.3.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-closest-point + + + + + +## [0.3.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-closest-point@0.3.4...@thi.ng/geom-closest-point@0.3.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-closest-point + + + + + +## [0.3.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-closest-point@0.3.3...@thi.ng/geom-closest-point@0.3.4) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-closest-point + + + + + ## [0.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-closest-point@0.3.2...@thi.ng/geom-closest-point@0.3.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-closest-point diff --git a/packages/geom-closest-point/package.json b/packages/geom-closest-point/package.json index d21a34b04c..d248cd7023 100644 --- a/packages/geom-closest-point/package.json +++ b/packages/geom-closest-point/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-closest-point", - "version": "0.3.3", + "version": "0.3.9", "description": "Closest point / proximity helpers", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/math": "^1.5.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "ES6", diff --git a/packages/geom-closest-point/src/index.ts b/packages/geom-closest-point/src/index.ts index a5f35f9924..94883ac778 100644 --- a/packages/geom-closest-point/src/index.ts +++ b/packages/geom-closest-point/src/index.ts @@ -228,19 +228,7 @@ export const closestPointRect = ( bmax: ReadonlyVec, out: Vec = [] ) => { - let minD = Infinity; - let minID: number; - let minW: number; - for (let i = 0; i < 4; i++) { - const j = i >> 1; - const w = (i & 1 ? bmax : bmin)[j]; - const d = Math.abs(p[j] - w); - if (d < minD) { - minD = d; - minID = j; - minW = w; - } - } + const [minID, minW] = closestBoxEdge(p, bmin, bmax, 4); return minID! === 0 ? setC2(out, minW!, clamp(p[1], bmin[1], bmax[1])) : setC2(out, clamp(p[0], bmin[0], bmax[0]), minW!); @@ -252,23 +240,11 @@ export const closestPointAABB = ( bmax: ReadonlyVec, out: Vec = [] ) => { - let minD = Infinity; - let minID: number; - let minW: number; - for (let i = 0; i < 6; i++) { - const j = i >> 1; - const w = (i & 1 ? bmax : bmin)[j]; - const d = Math.abs(p[j] - w); - if (d < minD) { - minD = d; - minID = j; - minW = w; - } - } + const [minID, minW] = closestBoxEdge(p, bmin, bmax, 6); return minID! === 0 ? setC3( out, - minW!, + minW, clamp(p[1], bmin[1], bmax[1]), clamp(p[2], bmin[2], bmax[2]) ) @@ -276,13 +252,35 @@ export const closestPointAABB = ( ? setC3( out, clamp(p[0], bmin[0], bmax[0]), - minW!, + minW, clamp(p[2], bmin[2], bmax[2]) ) : setC3( out, clamp(p[0], bmin[0], bmax[0]), clamp(p[1], bmin[1], bmax[1]), - minW! + minW ); }; + +const closestBoxEdge = ( + p: ReadonlyVec, + bmin: ReadonlyVec, + bmax: ReadonlyVec, + n: number +) => { + let minD = Infinity; + let minID: number; + let minW: number; + for (let i = 0; i < n; i++) { + const j = i >> 1; + const w = (i & 1 ? bmax : bmin)[j]; + const d = Math.abs(p[j] - w); + if (d < minD) { + minD = d; + minID = j; + minW = w; + } + } + return [minID!, minW!]; +}; diff --git a/packages/geom-hull/CHANGELOG.md b/packages/geom-hull/CHANGELOG.md index 7aa95d0c28..43f77a9d0a 100644 --- a/packages/geom-hull/CHANGELOG.md +++ b/packages/geom-hull/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.0.29](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-hull@0.0.28...@thi.ng/geom-hull@0.0.29) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-hull + + + + + +## [0.0.28](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-hull@0.0.27...@thi.ng/geom-hull@0.0.28) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-hull + + + + + +## [0.0.27](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-hull@0.0.26...@thi.ng/geom-hull@0.0.27) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-hull + + + + + +## [0.0.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-hull@0.0.25...@thi.ng/geom-hull@0.0.26) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-hull + + + + + +## [0.0.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-hull@0.0.24...@thi.ng/geom-hull@0.0.25) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-hull + + + + + +## [0.0.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-hull@0.0.23...@thi.ng/geom-hull@0.0.24) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-hull + + + + + ## [0.0.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-hull@0.0.22...@thi.ng/geom-hull@0.0.23) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-hull diff --git a/packages/geom-hull/package.json b/packages/geom-hull/package.json index 8df79d363b..5a3fe9ee4f 100644 --- a/packages/geom-hull/package.json +++ b/packages/geom-hull/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-hull", - "version": "0.0.23", + "version": "0.0.29", "description": "Fast 2D convex hull (Graham Scan)", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/math": "^1.5.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/geom-isec/CHANGELOG.md b/packages/geom-isec/CHANGELOG.md index 613589d692..b5d1a5ab84 100644 --- a/packages/geom-isec/CHANGELOG.md +++ b/packages/geom-isec/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isec@0.3.8...@thi.ng/geom-isec@0.3.9) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-isec + + + + + +## [0.3.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isec@0.3.7...@thi.ng/geom-isec@0.3.8) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-isec + + + + + +## [0.3.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isec@0.3.6...@thi.ng/geom-isec@0.3.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-isec + + + + + +## [0.3.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isec@0.3.5...@thi.ng/geom-isec@0.3.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-isec + + + + + +## [0.3.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isec@0.3.4...@thi.ng/geom-isec@0.3.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-isec + + + + + +## [0.3.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isec@0.3.3...@thi.ng/geom-isec@0.3.4) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-isec + + + + + ## [0.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isec@0.3.2...@thi.ng/geom-isec@0.3.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-isec diff --git a/packages/geom-isec/package.json b/packages/geom-isec/package.json index 499f424087..b36735bdf8 100644 --- a/packages/geom-isec/package.json +++ b/packages/geom-isec/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-isec", - "version": "0.3.3", + "version": "0.3.9", "description": "2D/3D shape intersection checks", "module": "./index.js", "main": "./lib/index.js", @@ -29,15 +29,15 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/geom-api": "^0.3.1", - "@thi.ng/geom-closest-point": "^0.3.3", - "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/api": "^6.5.0", + "@thi.ng/geom-api": "^0.3.7", + "@thi.ng/geom-closest-point": "^0.3.9", + "@thi.ng/math": "^1.5.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/geom-isec/src/ray-rect.ts b/packages/geom-isec/src/ray-rect.ts index 05c36d97df..81f98878d5 100644 --- a/packages/geom-isec/src/ray-rect.ts +++ b/packages/geom-isec/src/ray-rect.ts @@ -21,13 +21,14 @@ const rayRect = ( bmin: ReadonlyVec, bmax: ReadonlyVec ) => { - let p = rpos[0], - d = 1 / dir[0]; + let p = rpos[0]; + let d = 1 / dir[0]; let t1 = (bmin[0] - p) * d; let t2 = (bmax[0] - p) * d; let tmin = min(t1, t2); let tmax = max(t1, t2); - (p = rpos[1]), (d = 1 / dir[1]); + p = rpos[1]; + d = 1 / dir[1]; t1 = (bmin[1] - p) * d; t2 = (bmax[1] - p) * d; return <[number, number]>[max(tmin, min(t1, t2)), min(tmax, max(t1, t2))]; @@ -47,16 +48,18 @@ const rayBox = ( bmin: ReadonlyVec, bmax: ReadonlyVec ) => { - let p = rpos[0], - d = 1 / dir[0]; + let p = rpos[0]; + let d = 1 / dir[0]; let t1 = (bmin[0] - p) * d; let t2 = (bmax[0] - p) * d; let tmin = min(t1, t2); let tmax = max(t1, t2); - (p = rpos[1]), (d = 1 / dir[1]); + p = rpos[1]; + d = 1 / dir[1]; t1 = (bmin[1] - p) * d; t2 = (bmax[1] - p) * d; - (p = rpos[2]), (d = 1 / dir[2]); + p = rpos[2]; + d = 1 / dir[2]; t1 = (bmin[2] - p) * d; t2 = (bmax[2] - p) * d; tmin = max(tmin, min(t1, t2)); diff --git a/packages/geom-isoline/CHANGELOG.md b/packages/geom-isoline/CHANGELOG.md index 24c44253bb..3485eab0e4 100644 --- a/packages/geom-isoline/CHANGELOG.md +++ b/packages/geom-isoline/CHANGELOG.md @@ -3,6 +3,57 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.27](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isoline@0.1.26...@thi.ng/geom-isoline@0.1.27) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-isoline + + + + + +## [0.1.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isoline@0.1.25...@thi.ng/geom-isoline@0.1.26) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-isoline + + + + + +## [0.1.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isoline@0.1.24...@thi.ng/geom-isoline@0.1.25) (2019-08-21) + + +### Performance Improvements + +* **geom-isoline:** refactor contourVertex as jump table, minor updates ([d25827e](https://github.com/thi-ng/umbrella/commit/d25827e)) + + + + + +## [0.1.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isoline@0.1.23...@thi.ng/geom-isoline@0.1.24) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-isoline + + + + + +## [0.1.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isoline@0.1.22...@thi.ng/geom-isoline@0.1.23) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-isoline + + + + + +## [0.1.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isoline@0.1.21...@thi.ng/geom-isoline@0.1.22) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-isoline + + + + + ## [0.1.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-isoline@0.1.20...@thi.ng/geom-isoline@0.1.21) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-isoline diff --git a/packages/geom-isoline/README.md b/packages/geom-isoline/README.md index 1fb2dc8f68..18ad0c2fda 100644 --- a/packages/geom-isoline/README.md +++ b/packages/geom-isoline/README.md @@ -42,7 +42,7 @@ Animated version: [Live demo](https://demo.thi.ng/umbrella/iso-plasma/) | [Source code](https://github.com/thi-ng/umbrella/tree/master/examples/iso-plasma) -![example output](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-isoline.png) +![example output](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/geom/geom-isoline.png) ```ts import * as g from "@thi.ng/geom"; diff --git a/packages/geom-isoline/package.json b/packages/geom-isoline/package.json index 3a1afbb826..b593b051fe 100644 --- a/packages/geom-isoline/package.json +++ b/packages/geom-isoline/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-isoline", - "version": "0.1.21", + "version": "0.1.27", "description": "Fast 2D contour line extraction / generation", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/geom-isoline/src/index.ts b/packages/geom-isoline/src/index.ts index ca88d0472e..1c1761984d 100644 --- a/packages/geom-isoline/src/index.ts +++ b/packages/geom-isoline/src/index.ts @@ -1,41 +1,13 @@ +import { Fn5 } from "@thi.ng/api"; import { range2d } from "@thi.ng/transducers"; import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -// flattened [to,clear] tuples +// flattened [to, clear] tuples // all positive values are given as times 2 +// prettier-ignore const EDGE_INDEX = [ - -1, - -1, - 4, - 0, - 2, - 0, - 2, - 0, - 0, - 0, - -1, - -1, - 0, - 0, - 0, - 0, - 6, - 0, - 4, - 0, - -1, - -1, - 2, - 0, - 6, - 0, - 4, - 0, - 6, - 0, - -1, - -1 + -1, -1, 4, 0, 2, 0, 2, 0, 0, 0, -1, -1, 0, 0, 0, 0, + 6, 0, 4, 0, -1, -1, 2, 0, 6, 0, 4, 0, 6, 0, -1, -1 ]; // flattened coord offsets [x,y] tuples @@ -99,30 +71,17 @@ const mix = ( return a === b ? 0 : (a - iso) / (a - b); }; -const contourVertex = ( - src: ReadonlyVec, - w: number, - x: number, - y: number, - to: number, - iso: number -) => { - switch (to) { - case 0: - return [x + mix(src, w, x, y, x + 1, y, iso), y]; - case 2: - return [x + 1, y + mix(src, w, x + 1, y, x + 1, y + 1, iso)]; - case 4: - return [x + mix(src, w, x, y + 1, x + 1, y + 1, iso), y + 1]; - case 6: - return [x, y + mix(src, w, x, y, x, y + 1, iso)]; - default: - } -}; +// prettier-ignore +const contourVertex: Fn5[] = [ + (src, w, x, y, iso) => [x + mix(src, w, x, y, x + 1, y, iso), y], + (src, w, x, y, iso) => [x + 1, y + mix(src, w, x + 1, y, x + 1, y + 1, iso)], + (src, w, x, y, iso) => [x + mix(src, w, x, y + 1, x + 1, y + 1, iso), y + 1], + (src, w, x, y, iso) => [x, y + mix(src, w, x, y, x, y + 1, iso)] +]; export function* isolines(src: ReadonlyVec, w: number, h: number, iso: number) { const coded = encodeCrossings(src, w, h, iso); - let curr: number[][] = []; + let curr: Vec[] = []; let from: number; let to = -1; let clear: number; @@ -166,15 +125,15 @@ export function* isolines(src: ReadonlyVec, w: number, h: number, iso: number) { to = EDGE_INDEX[id]; clear = EDGE_INDEX[id + 1]; } - if (curr.length > 0 && from === -1 && to > -1) { + if (from === -1 && to > -1 && curr.length > 0) { yield curr; curr = []; } if (clear !== -1) { - coded[y * w + x] = clear; + coded[i] = clear; } if (to >= 0) { - curr.push(contourVertex(src, w, x, y, to, iso)!); + curr.push(contourVertex[to >> 1](src, w, x, y, iso)); x += NEXT_EDGES[to]; y += NEXT_EDGES[to + 1]; } else { diff --git a/packages/geom-poly-utils/CHANGELOG.md b/packages/geom-poly-utils/CHANGELOG.md index 8492873e46..1def0329a1 100644 --- a/packages/geom-poly-utils/CHANGELOG.md +++ b/packages/geom-poly-utils/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.27](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-poly-utils@0.1.26...@thi.ng/geom-poly-utils@0.1.27) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-poly-utils + + + + + +## [0.1.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-poly-utils@0.1.25...@thi.ng/geom-poly-utils@0.1.26) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-poly-utils + + + + + +## [0.1.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-poly-utils@0.1.24...@thi.ng/geom-poly-utils@0.1.25) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-poly-utils + + + + + +## [0.1.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-poly-utils@0.1.23...@thi.ng/geom-poly-utils@0.1.24) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-poly-utils + + + + + +## [0.1.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-poly-utils@0.1.22...@thi.ng/geom-poly-utils@0.1.23) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-poly-utils + + + + + +## [0.1.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-poly-utils@0.1.21...@thi.ng/geom-poly-utils@0.1.22) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-poly-utils + + + + + ## [0.1.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-poly-utils@0.1.20...@thi.ng/geom-poly-utils@0.1.21) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-poly-utils diff --git a/packages/geom-poly-utils/package.json b/packages/geom-poly-utils/package.json index 316fb9d56b..9ae4d5fd56 100644 --- a/packages/geom-poly-utils/package.json +++ b/packages/geom-poly-utils/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-poly-utils", - "version": "0.1.21", + "version": "0.1.27", "description": "Polygon / triangle analysis & processing utilities", "module": "./index.js", "main": "./lib/index.js", @@ -29,14 +29,14 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/errors": "^1.1.2", - "@thi.ng/geom-api": "^0.3.1", - "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/errors": "^1.2.1", + "@thi.ng/geom-api": "^0.3.7", + "@thi.ng/math": "^1.5.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/geom-resample/CHANGELOG.md b/packages/geom-resample/CHANGELOG.md index a7f0eb99b4..9992945bee 100644 --- a/packages/geom-resample/CHANGELOG.md +++ b/packages/geom-resample/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-resample@0.2.8...@thi.ng/geom-resample@0.2.9) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-resample + + + + + +## [0.2.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-resample@0.2.7...@thi.ng/geom-resample@0.2.8) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-resample + + + + + +## [0.2.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-resample@0.2.6...@thi.ng/geom-resample@0.2.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-resample + + + + + +## [0.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-resample@0.2.5...@thi.ng/geom-resample@0.2.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-resample + + + + + +## [0.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-resample@0.2.4...@thi.ng/geom-resample@0.2.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-resample + + + + + +## [0.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-resample@0.2.3...@thi.ng/geom-resample@0.2.4) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-resample + + + + + ## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-resample@0.2.2...@thi.ng/geom-resample@0.2.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-resample diff --git a/packages/geom-resample/package.json b/packages/geom-resample/package.json index cedddca481..077c19503b 100644 --- a/packages/geom-resample/package.json +++ b/packages/geom-resample/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-resample", - "version": "0.2.3", + "version": "0.2.9", "description": "Customizable nD polyline interpolation, re-sampling, splitting & nearest point computation", "module": "./index.js", "main": "./lib/index.js", @@ -29,15 +29,15 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/checks": "^2.2.2", - "@thi.ng/geom-api": "^0.3.1", - "@thi.ng/geom-closest-point": "^0.3.3", - "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/checks": "^2.4.1", + "@thi.ng/geom-api": "^0.3.7", + "@thi.ng/geom-closest-point": "^0.3.9", + "@thi.ng/math": "^1.5.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/geom-splines/CHANGELOG.md b/packages/geom-splines/CHANGELOG.md index 65d3ba876b..3c9aaf7fcc 100644 --- a/packages/geom-splines/CHANGELOG.md +++ b/packages/geom-splines/CHANGELOG.md @@ -3,6 +3,57 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.4.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-splines@0.4.1...@thi.ng/geom-splines@0.4.2) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-splines + + + + + +## [0.4.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-splines@0.4.0...@thi.ng/geom-splines@0.4.1) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-splines + + + + + +# [0.4.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-splines@0.3.4...@thi.ng/geom-splines@0.4.0) (2019-08-21) + + +### Features + +* **geom-splines:** add cubicTangentAt / quadraticTangentAt() ([e1cf355](https://github.com/thi-ng/umbrella/commit/e1cf355)) + + + + + +## [0.3.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-splines@0.3.3...@thi.ng/geom-splines@0.3.4) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-splines + + + + + +## [0.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-splines@0.3.2...@thi.ng/geom-splines@0.3.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-splines + + + + + +## [0.3.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-splines@0.3.1...@thi.ng/geom-splines@0.3.2) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-splines + + + + + ## [0.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-splines@0.3.0...@thi.ng/geom-splines@0.3.1) (2019-07-31) diff --git a/packages/geom-splines/README.md b/packages/geom-splines/README.md index bc5de4f2f6..ca772201b7 100644 --- a/packages/geom-splines/README.md +++ b/packages/geom-splines/README.md @@ -58,8 +58,8 @@ In this mode the curve always goes through the midpoints each polygon edge, with the original polygon vertices being used to compute control points. -| Proportional tangent scale | Uniform tangent scale | -|------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------| +| Proportional tangent scale | Uniform tangent scale | +|-------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------| | ![](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/geom/geom-splines-cp-nonuni.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/geom/geom-splines-cp-uni.png) | #### Poly vertices as break points @@ -69,8 +69,8 @@ and additional control points are created via symmetric tangents at each poly vertex. The tangents themselves are computed via the bisector of each vertex corner, taking into the convexity of each poly vertex. -| Proportional tangent scale | Uniform tangent scale | -|------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------| +| Proportional tangent scale | Uniform tangent scale | +|-------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------| | ![](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/geom/geom-splines-bp-nonuni.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/geom/geom-splines-bp-uni.png) | ```ts diff --git a/packages/geom-splines/package.json b/packages/geom-splines/package.json index a30bbbb5e6..c146082517 100644 --- a/packages/geom-splines/package.json +++ b/packages/geom-splines/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-splines", - "version": "0.3.1", + "version": "0.4.2", "description": "nD cubic & quadratic curve analysis, conversion, interpolation, splitting", "module": "./index.js", "main": "./lib/index.js", @@ -29,16 +29,16 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/checks": "^2.2.2", - "@thi.ng/geom-api": "^0.3.1", - "@thi.ng/geom-arc": "^0.2.3", - "@thi.ng/geom-resample": "^0.2.3", - "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/checks": "^2.4.1", + "@thi.ng/geom-api": "^0.3.7", + "@thi.ng/geom-arc": "^0.2.9", + "@thi.ng/geom-resample": "^0.2.9", + "@thi.ng/math": "^1.5.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/geom-splines/src/cubic-bounds.ts b/packages/geom-splines/src/cubic-bounds.ts index 54c72f2995..591df19027 100644 --- a/packages/geom-splines/src/cubic-bounds.ts +++ b/packages/geom-splines/src/cubic-bounds.ts @@ -34,18 +34,17 @@ const axisBounds = ( if (h > 0) { h = Math.sqrt(h); - let t = k0 / (-k1 - h); - if (t > 0 && t < 1) { - const q = mixCubic(pa, pb, pc, pd, t); - min[i] = Math.min(min[i], q); - max[i] = Math.max(max[i], q); - } - t = k0 / (-k1 + h); - if (t > 0 && t < 1) { - const q = mixCubic(pa, pb, pc, pd, t); - min[i] = Math.min(min[i], q); - max[i] = Math.max(max[i], q); - } + + const update = (t: number) => { + if (t > 0 && t < 1) { + const q = mixCubic(pa, pb, pc, pd, t); + min[i] = Math.min(min[i], q); + max[i] = Math.max(max[i], q); + } + }; + + update(k0 / (-k1 - h)); + update(k0 / (-k1 + h)); } }; diff --git a/packages/geom-splines/src/cubic-from-controlpoints.ts b/packages/geom-splines/src/cubic-from-controlpoints.ts index 92f6c90cf7..924fd610d9 100644 --- a/packages/geom-splines/src/cubic-from-controlpoints.ts +++ b/packages/geom-splines/src/cubic-from-controlpoints.ts @@ -7,38 +7,43 @@ import { Vec } from "@thi.ng/vectors"; +const buildUniform = (segments: Vec[], t: number) => { + const res: Vec[][] = []; + for (let i = 0, n = segments.length - 2; i < n; i += 2) { + const a = segments[i]; + const b = segments[i + 1]; + const c = segments[i + 2]; + res.push([ + a, + add(null, direction([], a, b, t), a), + add(null, direction([], c, b, t), c), + c + ]); + } + return res; +}; + +const buildNonUniform = (segments: Vec[], t: number) => { + const res: Vec[][] = []; + for (let i = 0, n = segments.length - 2; i < n; i += 2) { + const a = segments[i]; + const b = segments[i + 1]; + const c = segments[i + 2]; + res.push([a, mixN([], a, b, t), mixN([], c, b, t), c]); + } + return res; +}; + export const closedCubicFromControlPoints = ( points: ReadonlyVec[], t = 1, uniform = false ) => { - const segments = []; + const segments: Vec[] = []; for (let i = 0, num = points.length; i < num; i++) { - const a = points[i]; - const b = points[(i + 1) % num]; - segments.push(mixN([], a, b, 0.5), set([], b)); + const q = points[(i + 1) % num]; + segments.push(mixN([], points[i], q, 0.5), set([], q)); } segments.push(segments[0]); - const res: Vec[][] = []; - if (uniform) { - for (let i = 0, n = segments.length - 2; i < n; i += 2) { - const a = segments[i]; - const b = segments[i + 1]; - const c = segments[i + 2]; - res.push([ - a, - add(null, direction([], a, b, t), a), - add(null, direction([], c, b, t), c), - c - ]); - } - } else { - for (let i = 0, n = segments.length - 2; i < n; i += 2) { - const a = segments[i]; - const b = segments[i + 1]; - const c = segments[i + 2]; - res.push([a, mixN([], a, b, t), mixN([], c, b, t), c]); - } - } - return res; + return uniform ? buildUniform(segments, t) : buildNonUniform(segments, t); }; diff --git a/packages/geom-splines/src/cubic-sample.ts b/packages/geom-splines/src/cubic-sample.ts index a178a1de20..ce7a39638c 100644 --- a/packages/geom-splines/src/cubic-sample.ts +++ b/packages/geom-splines/src/cubic-sample.ts @@ -1,40 +1,9 @@ -import { isNumber, isPlainObject } from "@thi.ng/checks"; -import { DEFAULT_SAMPLES, SamplingOpts } from "@thi.ng/geom-api"; -import { Sampler } from "@thi.ng/geom-resample"; -import { - mixCubic, - ReadonlyVec, - set, - Vec -} from "@thi.ng/vectors"; +import { mixCubic } from "@thi.ng/vectors"; +import { __sample } from "./internal/sample"; -export const sampleCubic = ( - pts: ReadonlyVec[], - opts?: number | Partial -): Vec[] => { - if (isPlainObject(opts) && (opts).dist !== undefined) { - return new Sampler( - sampleCubic(pts, (opts).num || DEFAULT_SAMPLES) - ).sampleUniform( - (opts).dist, - (opts).last !== false - ); - } - opts = isNumber(opts) - ? { - num: opts, - last: true - } - : { - num: DEFAULT_SAMPLES, - ...opts - }; - const res: Vec[] = []; - const [a, b, c, d] = pts; - const delta = 1 / opts.num!; - for (let t = 0; t < opts.num!; t++) { +export const sampleCubic = __sample((res, [a, b, c, d], num) => { + const delta = 1 / num; + for (let t = 0; t < num; t++) { res.push(mixCubic([], a, b, c, d, t * delta)); } - opts.last && res.push(set([], d)); - return res; -}; +}); diff --git a/packages/geom-splines/src/cubic-tangent.ts b/packages/geom-splines/src/cubic-tangent.ts new file mode 100644 index 0000000000..eaef8099c5 --- /dev/null +++ b/packages/geom-splines/src/cubic-tangent.ts @@ -0,0 +1,26 @@ +import { + addW4, + normalize, + ReadonlyVec, + Vec +} from "@thi.ng/vectors"; + +export const cubicTangentAt = ( + out: Vec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + d: ReadonlyVec, + t: number, + len = 1 +) => { + const s = 1 - t; + const ss = s * s; + const tt = t * t; + const ts2 = 2 * t * s; + return normalize( + out, + addW4(out, a, b, c, d, -3 * ss, 3 * (ss - ts2), 3 * (ts2 - tt), 3 * tt), + len + ); +}; diff --git a/packages/geom-splines/src/index.ts b/packages/geom-splines/src/index.ts index 97ddd77aa1..0b154dd18e 100644 --- a/packages/geom-splines/src/index.ts +++ b/packages/geom-splines/src/index.ts @@ -7,11 +7,13 @@ export * from "./cubic-line"; export * from "./cubic-quadratic"; export * from "./cubic-sample"; export * from "./cubic-split"; +export * from "./cubic-tangent"; export * from "./quadratic-bounds"; export * from "./quadratic-closest-point"; export * from "./quadratic-line"; export * from "./quadratic-sample"; export * from "./quadratic-split"; +export * from "./quadratic-tangent"; export * from "./point-at"; diff --git a/packages/geom-splines/src/internal/sample.ts b/packages/geom-splines/src/internal/sample.ts new file mode 100644 index 0000000000..3314d4a281 --- /dev/null +++ b/packages/geom-splines/src/internal/sample.ts @@ -0,0 +1,33 @@ +import { Fn3 } from "@thi.ng/api"; +import { isNumber, isPlainObject } from "@thi.ng/checks"; +import { DEFAULT_SAMPLES, SamplingOpts } from "@thi.ng/geom-api"; +import { Sampler } from "@thi.ng/geom-resample"; +import { ReadonlyVec, set, Vec } from "@thi.ng/vectors"; + +export const __sample = (sample: Fn3) => + function $( + pts: ReadonlyVec[], + opts?: number | Partial + ): Vec[] { + if (isPlainObject(opts) && (opts).dist !== undefined) { + return new Sampler( + $(pts, (opts).num || DEFAULT_SAMPLES) + ).sampleUniform( + (opts).dist, + (opts).last !== false + ); + } + opts = isNumber(opts) + ? { + num: opts, + last: true + } + : { + num: DEFAULT_SAMPLES, + ...opts + }; + const res: Vec[] = []; + sample(res, pts, opts.num!); + opts.last && res.push(set([], pts[pts.length - 1])); + return res; + }; diff --git a/packages/geom-splines/src/quadratic-sample.ts b/packages/geom-splines/src/quadratic-sample.ts index c2266fb303..0cc0c00107 100644 --- a/packages/geom-splines/src/quadratic-sample.ts +++ b/packages/geom-splines/src/quadratic-sample.ts @@ -1,40 +1,9 @@ -import { isNumber, isPlainObject } from "@thi.ng/checks"; -import { DEFAULT_SAMPLES, SamplingOpts } from "@thi.ng/geom-api"; -import { Sampler } from "@thi.ng/geom-resample"; -import { - mixQuadratic, - ReadonlyVec, - set, - Vec -} from "@thi.ng/vectors"; +import { mixQuadratic } from "@thi.ng/vectors"; +import { __sample } from "./internal/sample"; -export const sampleQuadratic = ( - points: ReadonlyVec[], - opts?: number | Partial -): Vec[] => { - if (isPlainObject(opts) && (opts).dist !== undefined) { - return new Sampler( - sampleQuadratic(points, (opts).num || DEFAULT_SAMPLES) - ).sampleUniform( - (opts).dist, - (opts).last !== false - ); - } - opts = isNumber(opts) - ? { - num: opts, - last: true - } - : { - num: DEFAULT_SAMPLES, - ...opts - }; - const res: Vec[] = []; - const delta = 1 / opts.num!; - const [a, b, c] = points; - for (let t = 0; t < opts.num!; t++) { +export const sampleQuadratic = __sample((res, [a, b, c], num) => { + const delta = 1 / num; + for (let t = 0; t < num; t++) { res.push(mixQuadratic([], a, b, c, t * delta)); } - opts.last && res.push(set([], c)); - return res; -}; +}); diff --git a/packages/geom-splines/src/quadratic-tangent.ts b/packages/geom-splines/src/quadratic-tangent.ts new file mode 100644 index 0000000000..0676da2e3a --- /dev/null +++ b/packages/geom-splines/src/quadratic-tangent.ts @@ -0,0 +1,21 @@ +import { + addW2, + normalize, + ReadonlyVec, + sub, + Vec +} from "@thi.ng/vectors"; + +export const quadraticTangentAt = ( + out: Vec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + t: number, + len = 1 +) => + normalize( + out, + addW2(out, sub(out, b, a), sub([], c, b), 2 * (1 - t), 2 * t), + len + ); diff --git a/packages/geom-subdiv-curve/CHANGELOG.md b/packages/geom-subdiv-curve/CHANGELOG.md index ac52843100..81402e84c8 100644 --- a/packages/geom-subdiv-curve/CHANGELOG.md +++ b/packages/geom-subdiv-curve/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-subdiv-curve@0.1.25...@thi.ng/geom-subdiv-curve@0.1.26) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-subdiv-curve + + + + + +## [0.1.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-subdiv-curve@0.1.24...@thi.ng/geom-subdiv-curve@0.1.25) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-subdiv-curve + + + + + +## [0.1.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-subdiv-curve@0.1.23...@thi.ng/geom-subdiv-curve@0.1.24) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-subdiv-curve + + + + + +## [0.1.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-subdiv-curve@0.1.22...@thi.ng/geom-subdiv-curve@0.1.23) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-subdiv-curve + + + + + +## [0.1.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-subdiv-curve@0.1.21...@thi.ng/geom-subdiv-curve@0.1.22) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-subdiv-curve + + + + + +## [0.1.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-subdiv-curve@0.1.20...@thi.ng/geom-subdiv-curve@0.1.21) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-subdiv-curve + + + + + ## [0.1.20](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-subdiv-curve@0.1.19...@thi.ng/geom-subdiv-curve@0.1.20) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-subdiv-curve diff --git a/packages/geom-subdiv-curve/package.json b/packages/geom-subdiv-curve/package.json index 968eed1894..018cd6ef10 100644 --- a/packages/geom-subdiv-curve/package.json +++ b/packages/geom-subdiv-curve/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-subdiv-curve", - "version": "0.1.20", + "version": "0.1.26", "description": "Freely customizable, iterative subdivision curves for open / closed input geometries", "module": "./index.js", "main": "./lib/index.js", @@ -29,13 +29,13 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/geom-api": "^0.3.1", - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/geom-api": "^0.3.7", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/geom-subdiv-curve/src/api.ts b/packages/geom-subdiv-curve/src/api.ts index 0bbacac2b4..721735b952 100644 --- a/packages/geom-subdiv-curve/src/api.ts +++ b/packages/geom-subdiv-curve/src/api.ts @@ -1,5 +1,5 @@ import { SubdivKernel } from "@thi.ng/geom-api"; -import { wrap } from "@thi.ng/transducers"; +import { wrapSides } from "@thi.ng/transducers"; import { mixN, ReadonlyVec } from "@thi.ng/vectors"; import { kernel3 } from "./kernels"; @@ -15,8 +15,8 @@ const THIRDS = ([a, b]: ReadonlyVec[]) => [ mixN([], a, b, 2 / 3) ]; -const wrap2 = (pts: ReadonlyVec[]) => wrap(pts, 1, false, true); -const wrap3 = (pts: ReadonlyVec[]) => wrap(pts, 1, true, true); +const wrap2 = (pts: ReadonlyVec[]) => wrapSides(pts, 0, 1); +const wrap3 = (pts: ReadonlyVec[]) => wrapSides(pts, 1, 1); /** * Splits each curve / line segment into halves at midpoint. Version for @@ -64,8 +64,8 @@ export const SUBDIV_CHAIKIN_OPEN: SubdivKernel = { i == 0 ? [pts[0], ...CHAIKIN_FIRST(pts)] : i === n - 3 - ? [...CHAIKIN_LAST(pts), pts[2]] - : CHAIKIN_MAIN(pts), + ? [...CHAIKIN_LAST(pts), pts[2]] + : CHAIKIN_MAIN(pts), size: 3 }; diff --git a/packages/geom-tessellate/CHANGELOG.md b/packages/geom-tessellate/CHANGELOG.md index 45dc6fe43b..a3a77c72c2 100644 --- a/packages/geom-tessellate/CHANGELOG.md +++ b/packages/geom-tessellate/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-tessellate@0.2.8...@thi.ng/geom-tessellate@0.2.9) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-tessellate + + + + + +## [0.2.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-tessellate@0.2.7...@thi.ng/geom-tessellate@0.2.8) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-tessellate + + + + + +## [0.2.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-tessellate@0.2.6...@thi.ng/geom-tessellate@0.2.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-tessellate + + + + + +## [0.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-tessellate@0.2.5...@thi.ng/geom-tessellate@0.2.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-tessellate + + + + + +## [0.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-tessellate@0.2.4...@thi.ng/geom-tessellate@0.2.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-tessellate + + + + + +## [0.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-tessellate@0.2.3...@thi.ng/geom-tessellate@0.2.4) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-tessellate + + + + + ## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-tessellate@0.2.2...@thi.ng/geom-tessellate@0.2.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-tessellate diff --git a/packages/geom-tessellate/package.json b/packages/geom-tessellate/package.json index 2b9355a055..37710b0250 100644 --- a/packages/geom-tessellate/package.json +++ b/packages/geom-tessellate/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-tessellate", - "version": "0.2.3", + "version": "0.2.9", "description": "2D/3D polygon tessellators", "module": "./index.js", "main": "./lib/index.js", @@ -29,16 +29,16 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/checks": "^2.2.2", - "@thi.ng/geom-api": "^0.3.1", - "@thi.ng/geom-isec": "^0.3.3", - "@thi.ng/geom-poly-utils": "^0.1.21", - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/checks": "^2.4.1", + "@thi.ng/geom-api": "^0.3.7", + "@thi.ng/geom-isec": "^0.3.9", + "@thi.ng/geom-poly-utils": "^0.1.27", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/geom-tessellate/src/edge-split.ts b/packages/geom-tessellate/src/edge-split.ts index 1af9b34bd2..8bd4487708 100644 --- a/packages/geom-tessellate/src/edge-split.ts +++ b/packages/geom-tessellate/src/edge-split.ts @@ -6,7 +6,7 @@ import { partition, push, transduce, - wrap + wrapSides } from "@thi.ng/transducers"; import { mixN, ReadonlyVec, Vec } from "@thi.ng/vectors"; @@ -21,6 +21,6 @@ export const edgeSplit: Tessellator = (points: ReadonlyVec[]) => { }) ), push(), - wrap(points, 1, false, true) + wrapSides(points, 0, 1) ); }; diff --git a/packages/geom-tessellate/src/inset.ts b/packages/geom-tessellate/src/inset.ts index a47805b00a..f42de07610 100644 --- a/packages/geom-tessellate/src/inset.ts +++ b/packages/geom-tessellate/src/inset.ts @@ -6,8 +6,8 @@ import { partition, push, transduce, - zip, - wrap + wrapSides, + zip } from "@thi.ng/transducers"; import { mixN, ReadonlyVec, Vec } from "@thi.ng/vectors"; @@ -20,6 +20,6 @@ export const tesselInset = (inset = 0.5, keepInterior = false): Tessellator => ( comp(partition(2, 1), map(([[a, b], [c, d]]) => [a, b, d, c])), push(), keepInterior ? [inner] : [], - wrap([...zip(points, inner)], 1, false, true) + wrapSides([...zip(points, inner)], 0, 1) ); }; diff --git a/packages/geom-tessellate/src/quad-fan.ts b/packages/geom-tessellate/src/quad-fan.ts index 84abdaccad..2d422b1467 100644 --- a/packages/geom-tessellate/src/quad-fan.ts +++ b/packages/geom-tessellate/src/quad-fan.ts @@ -6,7 +6,7 @@ import { partition, push, transduce, - wrap + wrapSides } from "@thi.ng/transducers"; import { mixN, ReadonlyVec, Vec } from "@thi.ng/vectors"; @@ -18,6 +18,6 @@ export const quadFan: Tessellator = (points: ReadonlyVec[]) => { map(([a, b, c]) => [mixN([], a, b, 0.5), b, mixN([], b, c, 0.5), p]) ), push(), - wrap(points, 1, true, true) + wrapSides(points) ); }; diff --git a/packages/geom-tessellate/src/rim-tris.ts b/packages/geom-tessellate/src/rim-tris.ts index cc07c8b30e..9593111a69 100644 --- a/packages/geom-tessellate/src/rim-tris.ts +++ b/packages/geom-tessellate/src/rim-tris.ts @@ -5,7 +5,7 @@ import { partition, push, transduce, - wrap, + wrapSides, zip } from "@thi.ng/transducers"; import { mixN, ReadonlyVec, Vec } from "@thi.ng/vectors"; @@ -14,12 +14,12 @@ export const rimTris: Tessellator = (points: ReadonlyVec[]) => { const edgeCentroids = transduce( comp(partition(2, 1), map((e) => mixN([], e[0], e[1], 0.5))), push(), - wrap(points, 1, false, true) + wrapSides(points, 0, 1) ); return transduce( comp(partition(2, 1), map((t) => [t[0][0], t[1][1], t[1][0]])), push(), [edgeCentroids], - wrap([...zip(edgeCentroids, points)], 1, true, false) + wrapSides([...zip(edgeCentroids, points)], 1, 0) ); }; diff --git a/packages/geom-tessellate/src/tri-fan.ts b/packages/geom-tessellate/src/tri-fan.ts index a1d132b970..9ca640445b 100644 --- a/packages/geom-tessellate/src/tri-fan.ts +++ b/packages/geom-tessellate/src/tri-fan.ts @@ -6,7 +6,7 @@ import { partition, push, transduce, - wrap + wrapSides } from "@thi.ng/transducers"; import { ReadonlyVec, Vec } from "@thi.ng/vectors"; @@ -15,6 +15,6 @@ export const triFan: Tessellator = (points: ReadonlyVec[]) => { return transduce( comp(partition(2, 1), map(([a, b]) => [a, b, c])), push(), - wrap(points, 1, false, true) + wrapSides(points, 0, 1) ); }; diff --git a/packages/geom-voronoi/CHANGELOG.md b/packages/geom-voronoi/CHANGELOG.md index d6308e6ac4..566bb301fc 100644 --- a/packages/geom-voronoi/CHANGELOG.md +++ b/packages/geom-voronoi/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.27](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-voronoi@0.1.26...@thi.ng/geom-voronoi@0.1.27) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom-voronoi + + + + + +## [0.1.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-voronoi@0.1.25...@thi.ng/geom-voronoi@0.1.26) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom-voronoi + + + + + +## [0.1.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-voronoi@0.1.24...@thi.ng/geom-voronoi@0.1.25) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom-voronoi + + + + + +## [0.1.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-voronoi@0.1.23...@thi.ng/geom-voronoi@0.1.24) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom-voronoi + + + + + +## [0.1.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-voronoi@0.1.22...@thi.ng/geom-voronoi@0.1.23) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom-voronoi + + + + + +## [0.1.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-voronoi@0.1.21...@thi.ng/geom-voronoi@0.1.22) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom-voronoi + + + + + ## [0.1.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom-voronoi@0.1.20...@thi.ng/geom-voronoi@0.1.21) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom-voronoi diff --git a/packages/geom-voronoi/README.md b/packages/geom-voronoi/README.md index fab8d1c6b7..98fbfe21aa 100644 --- a/packages/geom-voronoi/README.md +++ b/packages/geom-voronoi/README.md @@ -53,7 +53,7 @@ yarn add @thi.ng/geom-voronoi ## Usage examples -![example screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-voronoi.jpg) +![example screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/geom/geom-voronoi.jpg) ```ts import { DVMesh } from "@thi.ng/geom-voronoi"; diff --git a/packages/geom-voronoi/package.json b/packages/geom-voronoi/package.json index b350336b57..891e925187 100644 --- a/packages/geom-voronoi/package.json +++ b/packages/geom-voronoi/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom-voronoi", - "version": "0.1.21", + "version": "0.1.27", "description": "Fast, incremental 2D Delaunay & Voronoi mesh implementation", "module": "./index.js", "main": "./lib/index.js", @@ -29,18 +29,18 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/geom-clip": "^0.1.3", - "@thi.ng/geom-isec": "^0.3.3", - "@thi.ng/geom-poly-utils": "^0.1.21", - "@thi.ng/math": "^1.4.2", - "@thi.ng/quad-edge": "^0.2.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/geom-clip": "^0.1.9", + "@thi.ng/geom-isec": "^0.3.9", + "@thi.ng/geom-poly-utils": "^0.1.27", + "@thi.ng/math": "^1.5.0", + "@thi.ng/quad-edge": "^0.2.3", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/geom/CHANGELOG.md b/packages/geom/CHANGELOG.md index 5d18faca9b..a252fbd4fd 100644 --- a/packages/geom/CHANGELOG.md +++ b/packages/geom/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom@1.7.6...@thi.ng/geom@1.7.7) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/geom + + + + + +## [1.7.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom@1.7.5...@thi.ng/geom@1.7.6) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/geom + + + + + +## [1.7.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom@1.7.4...@thi.ng/geom@1.7.5) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/geom + + + + + +## [1.7.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom@1.7.3...@thi.ng/geom@1.7.4) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/geom + + + + + +## [1.7.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom@1.7.2...@thi.ng/geom@1.7.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/geom + + + + + +## [1.7.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom@1.7.1...@thi.ng/geom@1.7.2) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/geom + + + + + ## [1.7.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/geom@1.7.0...@thi.ng/geom@1.7.1) (2019-07-31) **Note:** Version bump only for package @thi.ng/geom diff --git a/packages/geom/package.json b/packages/geom/package.json index b17ca8dd96..cb880b573f 100644 --- a/packages/geom/package.json +++ b/packages/geom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/geom", - "version": "1.7.1", + "version": "1.7.7", "description": "2D geometry types, polymorphic operations, SVG generation", "module": "./index.js", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "build:test": "rimraf build && tsc -p test/tsconfig.json", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", - "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib ctors internal ops", "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", "pub": "yarn build:release && yarn publish --access public" }, @@ -29,35 +29,35 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/arrays": "^0.2.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/compose": "^1.3.2", - "@thi.ng/defmulti": "^1.1.2", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/geom-api": "^0.3.1", - "@thi.ng/geom-arc": "^0.2.3", - "@thi.ng/geom-clip": "^0.1.3", - "@thi.ng/geom-closest-point": "^0.3.3", - "@thi.ng/geom-hull": "^0.0.23", - "@thi.ng/geom-isec": "^0.3.3", - "@thi.ng/geom-poly-utils": "^0.1.21", - "@thi.ng/geom-resample": "^0.2.3", - "@thi.ng/geom-splines": "^0.3.1", - "@thi.ng/geom-subdiv-curve": "^0.1.20", - "@thi.ng/geom-tessellate": "^0.2.3", - "@thi.ng/hiccup": "^3.2.2", - "@thi.ng/hiccup-svg": "^3.2.3", - "@thi.ng/math": "^1.4.2", - "@thi.ng/matrices": "^0.5.3", - "@thi.ng/random": "^1.1.10", - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/api": "^6.5.0", + "@thi.ng/arrays": "^0.3.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/compose": "^1.3.5", + "@thi.ng/defmulti": "^1.2.0", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/geom-api": "^0.3.7", + "@thi.ng/geom-arc": "^0.2.9", + "@thi.ng/geom-clip": "^0.1.9", + "@thi.ng/geom-closest-point": "^0.3.9", + "@thi.ng/geom-hull": "^0.0.29", + "@thi.ng/geom-isec": "^0.3.9", + "@thi.ng/geom-poly-utils": "^0.1.27", + "@thi.ng/geom-resample": "^0.2.9", + "@thi.ng/geom-splines": "^0.4.2", + "@thi.ng/geom-subdiv-curve": "^0.1.26", + "@thi.ng/geom-tessellate": "^0.2.9", + "@thi.ng/hiccup": "^3.2.6", + "@thi.ng/hiccup-svg": "^3.3.2", + "@thi.ng/math": "^1.5.0", + "@thi.ng/matrices": "^0.5.9", + "@thi.ng/random": "^1.1.13", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", @@ -72,5 +72,8 @@ "publishConfig": { "access": "public" }, - "sideEffects": false + "sideEffects": false, + "thi.ng": { + "shortlink": false + } } diff --git a/packages/geom/src/api.ts b/packages/geom/src/api.ts deleted file mode 100644 index 2f26b4fb16..0000000000 --- a/packages/geom/src/api.ts +++ /dev/null @@ -1,573 +0,0 @@ -import { isNumber } from "@thi.ng/checks"; -import { equiv } from "@thi.ng/equiv"; -import { illegalState } from "@thi.ng/errors"; -import { - AABBLike, - Attribs, - IHiccupPathSegment, - IHiccupShape, - IShape, - PathSegment, - PCLike, - Type -} from "@thi.ng/geom-api"; -import { pointAt as arcPointAt, pointAtTheta as arcPointAtTheta } from "@thi.ng/geom-arc"; -import { - add2, - add3, - copyVectors, - maddN2, - set, - Vec -} from "@thi.ng/vectors"; - -export abstract class APC implements PCLike { - points: Vec[]; - attribs?: Attribs; - - constructor(points?: Vec[], attribs?: Attribs) { - this.points = points || []; - this.attribs = attribs; - } - - abstract get type(): number | string; - abstract copy(): IShape; - - *[Symbol.iterator]() { - yield* this.points; - } -} - -export class AABB implements AABBLike { - pos: Vec; - size: Vec; - attribs?: Attribs; - - constructor( - pos: Vec = [0, 0, 0], - size: number | Vec = 1, - attribs?: Attribs - ) { - this.pos = pos; - this.size = isNumber(size) ? [size, size, size] : size; - this.attribs = attribs; - } - - get type() { - return Type.AABB; - } - - copy() { - return new AABB(set([], this.pos), set([], this.size), { - ...this.attribs - }); - } - - max() { - return add3([], this.pos, this.size); - } -} - -export class Arc implements IHiccupShape, IHiccupPathSegment { - pos: Vec; - r: Vec; - start: number; - end: number; - axis: number; - xl: boolean; - cw: boolean; - attribs?: Attribs; - - constructor( - pos: Vec, - r: Vec, - axis: number, - start: number, - end: number, - xl = false, - cw = false, - attribs?: Attribs - ) { - this.pos = pos; - this.r = r; - this.axis = axis; - this.start = start; - this.end = end; - this.xl = xl; - this.cw = cw; - this.attribs = attribs; - } - - get type() { - return Type.ARC; - } - - copy() { - return new Arc( - set([], this.pos), - set([], this.r), - this.axis, - this.start, - this.end, - this.xl, - this.cw, - { ...this.attribs } - ); - } - - equiv(o: any) { - return ( - o instanceof Arc && - equiv(this.pos, o.pos) && - equiv(this.r, o.r) && - this.start === o.start && - this.end === o.end && - this.axis === o.axis && - this.xl === o.xl && - this.cw && - o.cw - ); - } - - pointAt(t: number, out: Vec = []) { - return arcPointAt( - this.pos, - this.r, - this.axis, - this.start, - this.end, - t, - out - ); - } - - pointAtTheta(theta: number, out: Vec = []) { - return arcPointAtTheta(this.pos, this.r, this.axis, theta, out); - } - - toHiccup() { - return [ - "path", - this.attribs, - [["M", this.pointAt(0)], ...this.toHiccupPathSegments()] - ]; - } - - toHiccupPathSegments() { - return [ - [ - "A", - this.r[0], - this.r[1], - this.axis, - this.xl, - this.cw, - this.pointAt(1) - ] - ]; - } -} - -export class Circle implements IHiccupShape { - pos: Vec; - r: number; - attribs?: Attribs; - - constructor(pos: Vec = [0, 0], r = 1, attribs?: Attribs) { - this.pos = pos; - this.r = r; - this.attribs = attribs; - } - - get type() { - return Type.CIRCLE; - } - - copy() { - return new Circle(set([], this.pos), this.r, { ...this.attribs }); - } - - toHiccup() { - return ["circle", this.attribs, this.pos, this.r]; - } -} - -export class Cubic extends APC implements IHiccupPathSegment { - get type() { - return Type.CUBIC; - } - - copy() { - return new Cubic(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return [ - "path", - this.attribs, - [["M", this.points[0]], ...this.toHiccupPathSegments()] - ]; - } - - toHiccupPathSegments() { - const pts = this.points; - return [["C", pts[1], pts[2], pts[3]]]; - } -} - -export class Ellipse implements IHiccupShape { - pos: Vec; - r: Vec; - attribs?: Attribs; - - constructor( - pos: Vec = [0, 0], - r: number | Vec = [1, 1], - attribs?: Attribs - ) { - this.pos = pos; - this.r = isNumber(r) ? [r, r] : r; - this.attribs = attribs; - } - - get type() { - return Type.ELLIPSE; - } - - copy() { - return new Ellipse(set([], this.pos), set([], this.r), { - ...this.attribs - }); - } - - toHiccup() { - return ["ellipse", this.attribs, this.pos, this.r]; - } -} - -export class Group implements IHiccupShape { - children: IHiccupShape[]; - attribs: Attribs; - - constructor(attribs: Attribs, children?: IHiccupShape[]) { - this.attribs = attribs; - this.children = children || []; - } - - get type() { - return Type.GROUP; - } - - *[Symbol.iterator]() { - yield* this.children; - } - - copy() { - return new Group({ ...this.attribs }, ( - this.children.map((c) => c.copy()) - )); - } - - equiv(o: any) { - return o instanceof Group && equiv(this.children, o.children); - } - - toHiccup() { - return ["g", this.attribs, ...this.children.map((x) => x.toHiccup())]; - } -} - -export class Line extends APC implements IHiccupShape, IHiccupPathSegment { - get type() { - return Type.LINE; - } - - copy() { - return new Line(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["line", this.attribs, this.points[0], this.points[1]]; - } - - toHiccupPathSegments() { - const [a, b] = this.points; - return [ - a[0] === b[0] ? ["V", b[1]] : a[1] === b[1] ? ["H", b[0]] : ["L", b] - ]; - } -} - -export class Path implements IHiccupShape { - segments: PathSegment[]; - closed: boolean; - attribs?: Attribs; - - constructor(segments?: PathSegment[], attribs?: Attribs) { - this.segments = segments || []; - this.attribs = attribs; - this.closed = false; - } - - get type() { - return Type.PATH; - } - - *[Symbol.iterator]() { - yield* this.segments; - } - - copy() { - const p = new Path([...this.segments], { ...this.attribs }); - p.closed = this.closed; - return p; - } - - equiv(o: any) { - return o instanceof Path && equiv(this.segments, o.segments); - } - - add(s: PathSegment) { - if (this.closed) illegalState("path already closed"); - this.segments.push(s); - } - - toHiccup() { - let dest: any[] = []; - const segments = this.segments; - const n = segments.length; - if (n > 1) { - dest.push(["M", segments[0].point]); - for (let i = 1; i < n; i++) { - dest = dest.concat(segments[i].geo!.toHiccupPathSegments()); - } - if (this.closed) { - dest.push(["Z"]); - } - } - return ["path", this.attribs || {}, dest]; - } -} - -export class Plane implements IHiccupShape { - normal: Vec; - w: number; - attribs?: Attribs; - - constructor(normal: Vec = [0, 1, 0], w = 0, attribs?: Attribs) { - this.normal = normal; - this.w = w; - this.attribs = attribs; - } - - get type() { - return Type.PLANE; - } - - copy() { - return new Plane(set([], this.normal), this.w, { ...this.attribs }); - } - - toHiccup() { - return ["plane", this.attribs, this.normal, this.w]; - } -} - -export class Points extends APC implements IHiccupShape { - get type() { - return Type.POINTS; - } - - copy() { - return new Points(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["points", this.attribs, this.points]; - } -} - -export class Polygon extends APC implements IHiccupShape { - get type() { - return Type.POLYGON; - } - - copy() { - return new Polygon(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["polygon", this.attribs, this.points]; - } -} - -export class Polyline extends APC implements IHiccupShape, IHiccupPathSegment { - get type() { - return Type.POLYLINE; - } - - copy() { - return new Polyline(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["polyline", { ...this.attribs, fill: "none" }, this.points]; - } - - toHiccupPathSegments() { - const res: any[] = []; - for (let pts = this.points, n = pts.length, i = 1; i < n; i++) { - res.push(["L", pts[i]]); - } - return res; - } -} - -export class Quad extends APC implements IHiccupShape { - get type() { - return Type.QUAD; - } - - copy() { - return new Quad(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["polygon", this.attribs, this.points]; - } -} -export class Quad3 extends APC implements IHiccupShape { - get type() { - return Type.QUAD3; - } - - copy() { - return new Quad3(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["polygon", this.attribs, this.points]; - } -} - -export class Quadratic extends APC implements IHiccupShape, IHiccupPathSegment { - get type() { - return Type.QUADRATIC; - } - - copy() { - return new Quadratic(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return [ - "path", - this.attribs, - [["M", this.points[0]], ...this.toHiccupPathSegments()] - ]; - } - - toHiccupPathSegments() { - const pts = this.points; - return [["Q", pts[1], pts[2]]]; - } -} - -export class Ray implements IHiccupShape { - pos: Vec; - dir: Vec; - attribs?: Attribs; - - constructor(pos: Vec, dir: Vec, attribs?: Attribs) { - this.pos = pos; - this.dir = dir; - this.attribs = attribs; - } - - get type() { - return Type.RAY; - } - - copy() { - return new Ray(set([], this.pos), set([], this.dir), { - ...this.attribs - }); - } - - toHiccup() { - return [ - "line", - this.attribs, - this.pos, - maddN2([], this.dir, 1e6, this.pos) - ]; - } -} - -export class Rect implements AABBLike, IHiccupShape { - pos: Vec; - size: Vec; - attribs?: Attribs; - - constructor(pos: Vec = [0, 0], size: number | Vec = 1, attribs?: Attribs) { - this.pos = pos; - this.size = isNumber(size) ? [size, size] : size; - this.attribs = attribs; - } - - get type() { - return Type.RECT; - } - - copy() { - return new Rect(set([], this.pos), set([], this.size), { - ...this.attribs - }); - } - - max() { - return add2([], this.pos, this.size); - } - - toHiccup() { - return ["rect", this.attribs, this.pos, this.size[0], this.size[1]]; - } -} - -export class Sphere implements IHiccupShape { - pos: Vec; - r: number; - attribs?: Attribs; - - constructor(pos: Vec = [0, 0, 0], r = 1, attribs?: Attribs) { - this.pos = pos; - this.r = r; - this.attribs = attribs; - } - - get type() { - return Type.SPHERE; - } - - copy() { - return new Sphere(set([], this.pos), this.r, { ...this.attribs }); - } - - toHiccup() { - return ["sphere", this.attribs, this.pos, this.r]; - } -} - -export class Triangle extends APC implements IHiccupShape { - get type() { - return Type.TRIANGLE; - } - - copy() { - return new Triangle(copyVectors(this.points), { ...this.attribs }); - } - - toHiccup() { - return ["polygon", this.attribs, this.points]; - } -} diff --git a/packages/geom/src/api/aabb.ts b/packages/geom/src/api/aabb.ts new file mode 100644 index 0000000000..ed5d0435b5 --- /dev/null +++ b/packages/geom/src/api/aabb.ts @@ -0,0 +1,36 @@ +import { isNumber } from "@thi.ng/checks"; +import { AABBLike, Attribs, Type } from "@thi.ng/geom-api"; +import { add3, set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class AABB implements AABBLike { + pos: Vec; + size: Vec; + attribs?: Attribs; + + constructor( + pos: Vec = [0, 0, 0], + size: number | Vec = 1, + attribs?: Attribs + ) { + this.pos = pos; + this.size = isNumber(size) ? [size, size, size] : size; + this.attribs = attribs; + } + + get type() { + return Type.AABB; + } + + copy(): AABB { + return new AABB( + set([], this.pos), + set([], this.size), + copyAttribs(this) + ); + } + + max() { + return add3([], this.pos, this.size); + } +} diff --git a/packages/geom/src/api/apc.ts b/packages/geom/src/api/apc.ts new file mode 100644 index 0000000000..1d6f35c044 --- /dev/null +++ b/packages/geom/src/api/apc.ts @@ -0,0 +1,19 @@ +import { Attribs, IShape, PCLike } from "@thi.ng/geom-api"; +import { Vec } from "@thi.ng/vectors"; + +export abstract class APC implements PCLike { + points: Vec[]; + attribs?: Attribs; + + constructor(points?: Vec[], attribs?: Attribs) { + this.points = points || []; + this.attribs = attribs; + } + + abstract get type(): number | string; + abstract copy(): IShape; + + *[Symbol.iterator]() { + yield* this.points; + } +} diff --git a/packages/geom/src/api/arc.ts b/packages/geom/src/api/arc.ts new file mode 100644 index 0000000000..59400b7e53 --- /dev/null +++ b/packages/geom/src/api/arc.ts @@ -0,0 +1,110 @@ +import { equiv } from "@thi.ng/equiv"; +import { + Attribs, + IHiccupPathSegment, + IHiccupShape, + Type +} from "@thi.ng/geom-api"; +import { pointAt as arcPointAt, pointAtTheta as arcPointAtTheta } from "@thi.ng/geom-arc"; +import { set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Arc implements IHiccupShape, IHiccupPathSegment { + pos: Vec; + r: Vec; + start: number; + end: number; + axis: number; + xl: boolean; + cw: boolean; + attribs?: Attribs; + + constructor( + pos: Vec, + r: Vec, + axis: number, + start: number, + end: number, + xl = false, + cw = false, + attribs?: Attribs + ) { + this.pos = pos; + this.r = r; + this.axis = axis; + this.start = start; + this.end = end; + this.xl = xl; + this.cw = cw; + this.attribs = attribs; + } + + get type() { + return Type.ARC; + } + + copy(): Arc { + return new Arc( + set([], this.pos), + set([], this.r), + this.axis, + this.start, + this.end, + this.xl, + this.cw, + copyAttribs(this) + ); + } + + equiv(o: any) { + return ( + o instanceof Arc && + equiv(this.pos, o.pos) && + equiv(this.r, o.r) && + this.start === o.start && + this.end === o.end && + this.axis === o.axis && + this.xl === o.xl && + this.cw && + o.cw + ); + } + + pointAt(t: number, out: Vec = []) { + return arcPointAt( + this.pos, + this.r, + this.axis, + this.start, + this.end, + t, + out + ); + } + + pointAtTheta(theta: number, out: Vec = []) { + return arcPointAtTheta(this.pos, this.r, this.axis, theta, out); + } + + toHiccup() { + return [ + "path", + this.attribs, + [["M", this.pointAt(0)], ...this.toHiccupPathSegments()] + ]; + } + + toHiccupPathSegments() { + return [ + [ + "A", + this.r[0], + this.r[1], + this.axis, + this.xl, + this.cw, + this.pointAt(1) + ] + ]; + } +} diff --git a/packages/geom/src/api/circle.ts b/packages/geom/src/api/circle.ts new file mode 100644 index 0000000000..e44d26c927 --- /dev/null +++ b/packages/geom/src/api/circle.ts @@ -0,0 +1,27 @@ +import { Attribs, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Circle implements IHiccupShape { + pos: Vec; + r: number; + attribs?: Attribs; + + constructor(pos: Vec = [0, 0], r = 1, attribs?: Attribs) { + this.pos = pos; + this.r = r; + this.attribs = attribs; + } + + get type() { + return Type.CIRCLE; + } + + copy(): Circle { + return new Circle(set([], this.pos), this.r, copyAttribs(this)); + } + + toHiccup() { + return ["circle", this.attribs, this.pos, this.r]; + } +} diff --git a/packages/geom/src/api/cubic.ts b/packages/geom/src/api/cubic.ts new file mode 100644 index 0000000000..58e844f554 --- /dev/null +++ b/packages/geom/src/api/cubic.ts @@ -0,0 +1,26 @@ +import { IHiccupPathSegment, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Cubic extends APC implements IHiccupPathSegment { + get type() { + return Type.CUBIC; + } + + copy(): Cubic { + return copyShape(Cubic, this); + } + + toHiccup() { + return [ + "path", + this.attribs, + [["M", this.points[0]], ...this.toHiccupPathSegments()] + ]; + } + + toHiccupPathSegments() { + const pts = this.points; + return [["C", pts[1], pts[2], pts[3]]]; + } +} diff --git a/packages/geom/src/api/ellipse.ts b/packages/geom/src/api/ellipse.ts new file mode 100644 index 0000000000..3825050391 --- /dev/null +++ b/packages/geom/src/api/ellipse.ts @@ -0,0 +1,36 @@ +import { isNumber } from "@thi.ng/checks"; +import { Attribs, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Ellipse implements IHiccupShape { + pos: Vec; + r: Vec; + attribs?: Attribs; + + constructor( + pos: Vec = [0, 0], + r: number | Vec = [1, 1], + attribs?: Attribs + ) { + this.pos = pos; + this.r = isNumber(r) ? [r, r] : r; + this.attribs = attribs; + } + + get type() { + return Type.ELLIPSE; + } + + copy(): Ellipse { + return new Ellipse( + set([], this.pos), + set([], this.r), + copyAttribs(this) + ); + } + + toHiccup() { + return ["ellipse", this.attribs, this.pos, this.r]; + } +} diff --git a/packages/geom/src/api/group.ts b/packages/geom/src/api/group.ts new file mode 100644 index 0000000000..a60221eb75 --- /dev/null +++ b/packages/geom/src/api/group.ts @@ -0,0 +1,35 @@ +import { equiv } from "@thi.ng/equiv"; +import { Attribs, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Group implements IHiccupShape { + children: IHiccupShape[]; + attribs: Attribs; + + constructor(attribs: Attribs, children?: IHiccupShape[]) { + this.attribs = attribs; + this.children = children || []; + } + + get type() { + return Type.GROUP; + } + + *[Symbol.iterator]() { + yield* this.children; + } + + copy(): Group { + return new Group(copyAttribs(this), ( + this.children.map((c) => c.copy()) + )); + } + + equiv(o: any) { + return o instanceof Group && equiv(this.children, o.children); + } + + toHiccup() { + return ["g", this.attribs, ...this.children.map((x) => x.toHiccup())]; + } +} diff --git a/packages/geom/src/api/line.ts b/packages/geom/src/api/line.ts new file mode 100644 index 0000000000..25d6fb6f49 --- /dev/null +++ b/packages/geom/src/api/line.ts @@ -0,0 +1,24 @@ +import { IHiccupPathSegment, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Line extends APC implements IHiccupShape, IHiccupPathSegment { + get type() { + return Type.LINE; + } + + copy(): Line { + return copyShape(Line, this); + } + + toHiccup() { + return ["line", this.attribs, this.points[0], this.points[1]]; + } + + toHiccupPathSegments() { + const [a, b] = this.points; + return [ + a[0] === b[0] ? ["V", b[1]] : a[1] === b[1] ? ["H", b[0]] : ["L", b] + ]; + } +} diff --git a/packages/geom/src/api/path.ts b/packages/geom/src/api/path.ts new file mode 100644 index 0000000000..5e5c8354d7 --- /dev/null +++ b/packages/geom/src/api/path.ts @@ -0,0 +1,60 @@ +import { equiv } from "@thi.ng/equiv"; +import { illegalState } from "@thi.ng/errors"; +import { + Attribs, + IHiccupShape, + PathSegment, + Type +} from "@thi.ng/geom-api"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Path implements IHiccupShape { + segments: PathSegment[]; + closed: boolean; + attribs?: Attribs; + + constructor(segments?: PathSegment[], attribs?: Attribs) { + this.segments = segments || []; + this.attribs = attribs; + this.closed = false; + } + + get type() { + return Type.PATH; + } + + *[Symbol.iterator]() { + yield* this.segments; + } + + copy(): Path { + const p = new Path([...this.segments], copyAttribs(this)); + p.closed = this.closed; + return p; + } + + equiv(o: any) { + return o instanceof Path && equiv(this.segments, o.segments); + } + + add(s: PathSegment) { + if (this.closed) illegalState("path already closed"); + this.segments.push(s); + } + + toHiccup() { + let dest: any[] = []; + const segments = this.segments; + const n = segments.length; + if (n > 1) { + dest.push(["M", segments[0].point]); + for (let i = 1; i < n; i++) { + dest = dest.concat(segments[i].geo!.toHiccupPathSegments()); + } + if (this.closed) { + dest.push(["Z"]); + } + } + return ["path", this.attribs || {}, dest]; + } +} diff --git a/packages/geom/src/api/plane.ts b/packages/geom/src/api/plane.ts new file mode 100644 index 0000000000..84a5865a29 --- /dev/null +++ b/packages/geom/src/api/plane.ts @@ -0,0 +1,27 @@ +import { Attribs, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Plane implements IHiccupShape { + normal: Vec; + w: number; + attribs?: Attribs; + + constructor(normal: Vec = [0, 1, 0], w = 0, attribs?: Attribs) { + this.normal = normal; + this.w = w; + this.attribs = attribs; + } + + get type() { + return Type.PLANE; + } + + copy(): Plane { + return new Plane(set([], this.normal), this.w, copyAttribs(this)); + } + + toHiccup() { + return ["plane", this.attribs, this.normal, this.w]; + } +} diff --git a/packages/geom/src/api/points.ts b/packages/geom/src/api/points.ts new file mode 100644 index 0000000000..ba713c78f9 --- /dev/null +++ b/packages/geom/src/api/points.ts @@ -0,0 +1,17 @@ +import { IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Points extends APC implements IHiccupShape { + get type() { + return Type.POINTS; + } + + copy(): Points { + return copyShape(Points, this); + } + + toHiccup() { + return ["points", this.attribs, this.points]; + } +} diff --git a/packages/geom/src/api/polygon.ts b/packages/geom/src/api/polygon.ts new file mode 100644 index 0000000000..bb91585e70 --- /dev/null +++ b/packages/geom/src/api/polygon.ts @@ -0,0 +1,17 @@ +import { IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Polygon extends APC implements IHiccupShape { + get type() { + return Type.POLYGON; + } + + copy(): Polygon { + return copyShape(Polygon, this); + } + + toHiccup() { + return ["polygon", this.attribs, this.points]; + } +} diff --git a/packages/geom/src/api/polyline.ts b/packages/geom/src/api/polyline.ts new file mode 100644 index 0000000000..3a46211222 --- /dev/null +++ b/packages/geom/src/api/polyline.ts @@ -0,0 +1,25 @@ +import { IHiccupPathSegment, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Polyline extends APC implements IHiccupShape, IHiccupPathSegment { + get type() { + return Type.POLYLINE; + } + + copy(): Polyline { + return copyShape(Polyline, this); + } + + toHiccup() { + return ["polyline", { ...this.attribs, fill: "none" }, this.points]; + } + + toHiccupPathSegments() { + const res: any[] = []; + for (let pts = this.points, n = pts.length, i = 1; i < n; i++) { + res.push(["L", pts[i]]); + } + return res; + } +} diff --git a/packages/geom/src/api/quad.ts b/packages/geom/src/api/quad.ts new file mode 100644 index 0000000000..d5718ec713 --- /dev/null +++ b/packages/geom/src/api/quad.ts @@ -0,0 +1,17 @@ +import { IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Quad extends APC implements IHiccupShape { + get type() { + return Type.QUAD; + } + + copy(): Quad { + return copyShape(Quad, this); + } + + toHiccup() { + return ["polygon", this.attribs, this.points]; + } +} diff --git a/packages/geom/src/api/quad3.ts b/packages/geom/src/api/quad3.ts new file mode 100644 index 0000000000..704bf86522 --- /dev/null +++ b/packages/geom/src/api/quad3.ts @@ -0,0 +1,17 @@ +import { IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Quad3 extends APC implements IHiccupShape { + get type() { + return Type.QUAD3; + } + + copy(): Quad3 { + return copyShape(Quad3, this); + } + + toHiccup() { + return ["polygon", this.attribs, this.points]; + } +} diff --git a/packages/geom/src/api/quadratic.ts b/packages/geom/src/api/quadratic.ts new file mode 100644 index 0000000000..b5ef14c09e --- /dev/null +++ b/packages/geom/src/api/quadratic.ts @@ -0,0 +1,26 @@ +import { IHiccupPathSegment, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Quadratic extends APC implements IHiccupShape, IHiccupPathSegment { + get type() { + return Type.QUADRATIC; + } + + copy(): Quadratic { + return copyShape(Quadratic, this); + } + + toHiccup() { + return [ + "path", + this.attribs, + [["M", this.points[0]], ...this.toHiccupPathSegments()] + ]; + } + + toHiccupPathSegments() { + const pts = this.points; + return [["Q", pts[1], pts[2]]]; + } +} diff --git a/packages/geom/src/api/ray.ts b/packages/geom/src/api/ray.ts new file mode 100644 index 0000000000..70e2380ea4 --- /dev/null +++ b/packages/geom/src/api/ray.ts @@ -0,0 +1,32 @@ +import { Attribs, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { maddN2, set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Ray implements IHiccupShape { + pos: Vec; + dir: Vec; + attribs?: Attribs; + + constructor(pos: Vec, dir: Vec, attribs?: Attribs) { + this.pos = pos; + this.dir = dir; + this.attribs = attribs; + } + + get type() { + return Type.RAY; + } + + copy(): Ray { + return new Ray(set([], this.pos), set([], this.dir), copyAttribs(this)); + } + + toHiccup() { + return [ + "line", + this.attribs, + this.pos, + maddN2([], this.dir, 1e6, this.pos) + ]; + } +} diff --git a/packages/geom/src/api/rect.ts b/packages/geom/src/api/rect.ts new file mode 100644 index 0000000000..1f4985c1a5 --- /dev/null +++ b/packages/geom/src/api/rect.ts @@ -0,0 +1,41 @@ +import { isNumber } from "@thi.ng/checks"; +import { + AABBLike, + Attribs, + IHiccupShape, + Type +} from "@thi.ng/geom-api"; +import { add2, set, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Rect implements AABBLike, IHiccupShape { + pos: Vec; + size: Vec; + attribs?: Attribs; + + constructor(pos: Vec = [0, 0], size: number | Vec = 1, attribs?: Attribs) { + this.pos = pos; + this.size = isNumber(size) ? [size, size] : size; + this.attribs = attribs; + } + + get type() { + return Type.RECT; + } + + copy(): Rect { + return new Rect( + set([], this.pos), + set([], this.size), + copyAttribs(this) + ); + } + + max() { + return add2([], this.pos, this.size); + } + + toHiccup() { + return ["rect", this.attribs, this.pos, this.size[0], this.size[1]]; + } +} diff --git a/packages/geom/src/api/sphere.ts b/packages/geom/src/api/sphere.ts new file mode 100644 index 0000000000..95f5f5aaf7 --- /dev/null +++ b/packages/geom/src/api/sphere.ts @@ -0,0 +1,27 @@ +import { Attribs, IHiccupShape, Type } from "@thi.ng/geom-api"; +import { set3, Vec } from "@thi.ng/vectors"; +import { copyAttribs } from "../internal/copy-attribs"; + +export class Sphere implements IHiccupShape { + pos: Vec; + r: number; + attribs?: Attribs; + + constructor(pos: Vec = [0, 0, 0], r = 1, attribs?: Attribs) { + this.pos = pos; + this.r = r; + this.attribs = attribs; + } + + get type() { + return Type.SPHERE; + } + + copy(): Sphere { + return new Sphere(set3([], this.pos), this.r, copyAttribs(this)); + } + + toHiccup() { + return ["sphere", this.attribs, this.pos, this.r]; + } +} diff --git a/packages/geom/src/api/triangle.ts b/packages/geom/src/api/triangle.ts new file mode 100644 index 0000000000..ce078c0dec --- /dev/null +++ b/packages/geom/src/api/triangle.ts @@ -0,0 +1,17 @@ +import { IHiccupShape, Type } from "@thi.ng/geom-api"; +import { copyShape } from "../internal/copy-shape"; +import { APC } from "./apc"; + +export class Triangle extends APC implements IHiccupShape { + get type() { + return Type.TRIANGLE; + } + + copy(): Triangle { + return copyShape(Triangle, this); + } + + toHiccup() { + return ["polygon", this.attribs, this.points]; + } +} diff --git a/packages/geom/src/ctors/aabb.ts b/packages/geom/src/ctors/aabb.ts index f141834790..92333c4a49 100644 --- a/packages/geom/src/ctors/aabb.ts +++ b/packages/geom/src/ctors/aabb.ts @@ -6,7 +6,8 @@ import { subN3, Vec } from "@thi.ng/vectors"; -import { AABB, Sphere } from "../api"; +import { AABB } from "../api/aabb"; +import { Sphere } from "../api/sphere"; import { argsVV } from "../internal/args"; export function aabb(pos: Vec, size: number | Vec, attribs?: Attribs): AABB; diff --git a/packages/geom/src/ctors/arc.ts b/packages/geom/src/ctors/arc.ts index 145418b32a..076a38b60e 100644 --- a/packages/geom/src/ctors/arc.ts +++ b/packages/geom/src/ctors/arc.ts @@ -2,7 +2,7 @@ import { isNumber } from "@thi.ng/checks"; import { Attribs } from "@thi.ng/geom-api"; import { fromEndPoints } from "@thi.ng/geom-arc"; import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { Arc } from "../api"; +import { Arc } from "../api/arc"; export const arc = ( pos: Vec, diff --git a/packages/geom/src/ctors/circle.ts b/packages/geom/src/ctors/circle.ts index dc6ff4aba6..ee9fca21d6 100644 --- a/packages/geom/src/ctors/circle.ts +++ b/packages/geom/src/ctors/circle.ts @@ -1,7 +1,12 @@ import { Attribs } from "@thi.ng/geom-api"; import { circumCenter2 } from "@thi.ng/geom-poly-utils"; -import { dist, mixN2, ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { Circle } from "../api"; +import { + dist, + mixN2, + ReadonlyVec, + Vec +} from "@thi.ng/vectors"; +import { Circle } from "../api/circle"; import { argsVN } from "../internal/args"; export function circle(pos: Vec, r: number, attribs?: Attribs): Circle; diff --git a/packages/geom/src/ctors/cubic.ts b/packages/geom/src/ctors/cubic.ts index 18b542a601..177dcd807b 100644 --- a/packages/geom/src/ctors/cubic.ts +++ b/packages/geom/src/ctors/cubic.ts @@ -1,23 +1,20 @@ import { Attribs } from "@thi.ng/geom-api"; -import { - cubicFromArc as _arc, - cubicFromLine as _line, - cubicFromQuadratic as _quad -} from "@thi.ng/geom-splines"; +import { cubicFromArc as _arc, cubicFromLine as _line, cubicFromQuadratic as _quad } from "@thi.ng/geom-splines"; import { Vec } from "@thi.ng/vectors"; -import { Arc, Cubic } from "../api"; -import { argAttribs } from "../internal/args"; +import { Arc } from "../api/arc"; +import { Cubic } from "../api/cubic"; +import { copyAttribs } from "../internal/copy-attribs"; +import { pclike } from "../internal/pclike"; export function cubic(a: Vec, b: Vec, c: Vec, d: Vec, attribs?: Attribs): Cubic; export function cubic(pts: Vec[], attribs?: Attribs): Cubic; export function cubic(...args: any[]) { - const attr = argAttribs(args); - return new Cubic(args.length === 1 ? args[0] : args, attr); + return pclike(Cubic, args); } export const cubicFromArc = (arc: Arc) => _arc(arc.pos, arc.r, arc.axis, arc.start, arc.end).map( - (c) => new Cubic(c, { ...arc.attribs }) + (c) => new Cubic(c, copyAttribs(arc)) ); export const cubicFromLine = (a: Vec, b: Vec, attribs?: Attribs) => diff --git a/packages/geom/src/ctors/ellipse.ts b/packages/geom/src/ctors/ellipse.ts index a0b7775136..756a638ea0 100644 --- a/packages/geom/src/ctors/ellipse.ts +++ b/packages/geom/src/ctors/ellipse.ts @@ -1,6 +1,6 @@ import { Attribs } from "@thi.ng/geom-api"; import { Vec } from "@thi.ng/vectors"; -import { Ellipse } from "../api"; +import { Ellipse } from "../api/ellipse"; import { argsVV } from "../internal/args"; export function ellipse(pos: Vec, r: number | Vec, attribs?: Attribs): Ellipse; diff --git a/packages/geom/src/ctors/group.ts b/packages/geom/src/ctors/group.ts index 74af0412c6..5a84ab27c4 100644 --- a/packages/geom/src/ctors/group.ts +++ b/packages/geom/src/ctors/group.ts @@ -1,5 +1,5 @@ import { Attribs, IHiccupShape } from "@thi.ng/geom-api"; -import { Group } from "../api"; +import { Group } from "../api/group"; export const group = (attribs: Attribs = {}, children?: IHiccupShape[]) => new Group(attribs, children); diff --git a/packages/geom/src/ctors/line.ts b/packages/geom/src/ctors/line.ts index 500b352d9b..b175452e61 100644 --- a/packages/geom/src/ctors/line.ts +++ b/packages/geom/src/ctors/line.ts @@ -1,14 +1,14 @@ import { Attribs } from "@thi.ng/geom-api"; import { liangBarsky2 } from "@thi.ng/geom-clip"; import { Vec, VecPair } from "@thi.ng/vectors"; -import { Line, Rect } from "../api"; -import { argAttribs } from "../internal/args"; +import { Line } from "../api/line"; +import { Rect } from "../api/rect"; +import { pclike } from "../internal/pclike"; export function line(a: Vec, b: Vec, attribs?: Attribs): Line; export function line(pts: Vec[], attribs?: Attribs): Line; export function line(...args: any[]) { - const attr = argAttribs(args); - return new Line(args.length === 1 ? args[0] : args, attr); + return pclike(Line, args); } export const clippedLine = (l: Line, bounds: VecPair | Rect) => { diff --git a/packages/geom/src/ctors/path-builder.ts b/packages/geom/src/ctors/path-builder.ts new file mode 100644 index 0000000000..11aa295d33 --- /dev/null +++ b/packages/geom/src/ctors/path-builder.ts @@ -0,0 +1,195 @@ +import { peek } from "@thi.ng/arrays"; +import { Attribs, SegmentType } from "@thi.ng/geom-api"; +import { eqDelta } from "@thi.ng/math"; +import { + add2, + copy, + mulN2, + set2, + sub2, + Vec, + zeroes +} from "@thi.ng/vectors"; +import { Cubic } from "../api/cubic"; +import { Line } from "../api/line"; +import { Path } from "../api/path"; +import { Quadratic } from "../api/quadratic"; +import { arcFrom2Points } from "./arc"; + +export class PathBuilder { + paths: Path[]; + attribs?: Attribs; + protected curr!: Path; + protected currP!: Vec; + protected bezierP!: Vec; + protected startP!: Vec; + + constructor(attribs?: Attribs) { + this.paths = []; + this.attribs = attribs; + this.newPath(); + } + + *[Symbol.iterator]() { + yield* this.paths; + } + + current() { + return this.curr; + } + + newPath() { + this.curr = new Path([], this.attribs); + this.paths.push(this.curr); + this.currP = zeroes(2); + this.bezierP = zeroes(2); + this.startP = zeroes(2); + } + + moveTo(p: Vec, relative = false): PathBuilder { + if (this.curr.segments.length > 0) { + this.curr = new Path(); + this.paths.push(this.curr); + } + p = this.updateCurrent(p, relative); + set2(this.startP, p); + set2(this.bezierP, p); + this.curr.add({ + point: p, + type: SegmentType.MOVE + }); + return this; + } + + lineTo(p: Vec, relative = false): PathBuilder { + this.curr.add({ + geo: new Line([copy(this.currP), this.updateCurrent(p, relative)]), + type: SegmentType.LINE + }); + set2(this.bezierP, this.currP); + return this; + } + + hlineTo(x: number, relative = false): PathBuilder { + this.addHVLine(x, 0, relative); + return this; + } + + vlineTo(y: number, relative = false): PathBuilder { + this.addHVLine(y, 1, relative); + return this; + } + + // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Cubic_B%C3%A9zier_Curve + cubicTo(cp1: Vec, cp2: Vec, p: Vec, relative = false) { + this.addCubic(this.absPoint(cp1, relative), cp2, p, relative); + return this; + } + + // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Quadratic_B%C3%A9zier_Curve + quadraticTo(cp: Vec, p: Vec, relative = false) { + this.addQuadratic(this.absPoint(cp, relative), p, relative); + return this; + } + + cubicChainTo(cp2: Vec, p: Vec, relative = false) { + const prevMode = peek(this.curr.segments).type; + const c1 = copy(this.currP); + prevMode === SegmentType.CUBIC && + add2(null, sub2([], c1, this.bezierP), c1); + this.addCubic(c1, cp2, p, relative); + return this; + } + + quadraticChainTo(p: Vec, relative = false) { + const prevMode = peek(this.curr.segments).type; + const c1 = copy(this.currP); + prevMode === SegmentType.QUADRATIC && + sub2(null, mulN2(null, c1, 2), this.bezierP); + this.addQuadratic(c1, p, relative); + return this; + } + + arcTo( + p: Vec, + r: Vec, + xaxis: number, + xl: boolean, + clockwise: boolean, + relative = false + ) { + if (eqDelta(r[0], 0) || eqDelta(r[1], 0)) { + return this.lineTo(p, relative); + } + const prev = copy(this.currP); + this.curr.add({ + geo: arcFrom2Points( + prev, + this.updateCurrent(p, relative), + r, + xaxis, + xl, + clockwise + ), + type: SegmentType.ARC + }); + set2(this.bezierP, this.currP); + return this; + } + + closePath() { + this.curr.add({ + geo: new Line([copy(this.currP), copy(this.startP)]), + type: SegmentType.LINE + }); + this.curr.closed = true; + return this; + } + + protected updateCurrent(p: Vec, relative: boolean) { + p = copy(relative ? add2(null, this.currP, p) : set2(this.currP, p)); + return p; + } + + protected absPoint(p: Vec, relative: boolean) { + return relative ? add2(null, p, this.currP) : p; + } + + protected addHVLine(p: number, i: number, relative: boolean) { + const prev = copy(this.currP); + this.currP[i] = relative ? this.currP[i] + p : p; + set2(this.bezierP, this.currP); + this.curr.add({ + geo: new Line([prev, copy(this.currP)]), + type: SegmentType.LINE + }); + } + + protected addCubic(cp1: Vec, cp2: Vec, p: Vec, relative: boolean) { + cp2 = this.absPoint(cp2, relative); + set2(this.bezierP, cp2); + this.curr.add({ + geo: new Cubic([ + copy(this.currP), + cp1, + cp2, + this.updateCurrent(p, relative) + ]), + type: SegmentType.CUBIC + }); + } + + protected addQuadratic(cp: Vec, p: Vec, relative: boolean) { + set2(this.bezierP, cp); + this.curr.add({ + geo: new Quadratic([ + copy(this.currP), + cp, + this.updateCurrent(p, relative) + ]), + type: SegmentType.QUADRATIC + }); + } +} + +export const pathBuilder = (attribs?: Attribs) => new PathBuilder(attribs); diff --git a/packages/geom/src/ctors/path-from-svg.ts b/packages/geom/src/ctors/path-from-svg.ts new file mode 100644 index 0000000000..4937b6f3c0 --- /dev/null +++ b/packages/geom/src/ctors/path-from-svg.ts @@ -0,0 +1,160 @@ +import { illegalState } from "@thi.ng/errors"; +import { rad } from "@thi.ng/math"; +import { Vec } from "@thi.ng/vectors"; +import { PathBuilder } from "./path-builder"; + +const CMD_RE = /[achlmqstvz]/i; + +export const pathFromSvg = (svg: string) => { + const b = new PathBuilder(); + try { + let cmd = ""; + for (let n = svg.length, i = 0; i < n; ) { + i = skipWS(svg, i); + const c = svg.charAt(i); + if (CMD_RE.test(c)) { + cmd = c; + i++; + } + let p, pa, pb, t1, t2, t3; + switch (cmd.toLowerCase()) { + case "m": + [p, i] = readPoint(svg, i); + b.moveTo(p, cmd === "m"); + break; + case "l": + [p, i] = readPoint(svg, i); + b.lineTo(p, cmd === "l"); + break; + case "h": + [p, i] = readFloat(svg, i); + b.hlineTo(p, cmd === "h"); + break; + case "v": + [p, i] = readFloat(svg, i); + b.vlineTo(p, cmd === "v"); + break; + case "q": + [pa, i] = readPoint(svg, i); + [p, i] = readPoint(svg, i); + // console.log("quadratic", pa.toString(), p.toString()); + b.quadraticTo(pa, p, cmd === "q"); + break; + case "c": + [pa, i] = readPoint(svg, i); + [pb, i] = readPoint(svg, i); + [p, i] = readPoint(svg, i); + // console.log("cubic", pa.toString(), pb.toString(), p.toString()); + b.cubicTo(pa, pb, p, cmd === "c"); + break; + case "s": + [pa, i] = readPoint(svg, i); + [p, i] = readPoint(svg, i); + // console.log("cubicChain", pa.toString(), p.toString()); + b.cubicChainTo(pa, p, cmd === "s"); + break; + case "t": + [p, i] = readPoint(svg, i); + // console.log("quadraticChain", p.toString()); + b.quadraticChainTo(p, cmd === "t"); + break; + case "a": { + [pa, i] = readPoint(svg, i); + [t1, i] = readFloat(svg, i); + [t2, i] = readFlag(svg, i); + [t3, i] = readFlag(svg, i); + [pb, i] = readPoint(svg, i); + // console.log("arc", pa.toString(), rad(t1), t2, t3, pb.toString()); + b.arcTo(pb, pa, rad(t1), t2, t3, cmd === "a"); + break; + } + case "z": + b.closePath(); + break; + default: + throw new Error( + `unsupported segment type: ${c} @ pos ${i}` + ); + } + } + return b.paths; + } catch (e) { + throw e instanceof Error + ? e + : new Error(`illegal char '${svg.charAt(e)}' @ ${e}`); + } +}; + +const isWS = (c: string) => c === " " || c === "\n" || c === "\r" || c === "\t"; + +const skipWS = (src: string, i: number) => { + const n = src.length; + while (i < n && isWS(src.charAt(i))) i++; + return i; +}; + +const readPoint = (src: string, index: number): [Vec, number] => { + let x, y; + [x, index] = readFloat(src, index); + index = skipWS(src, index); + [y, index] = readFloat(src, index); + return [[x, y], index]; +}; + +const readFlag = (src: string, i: number): [boolean, number] => { + i = skipWS(src, i); + const c = src.charAt(i); + return [ + c === "0" + ? false + : c === "1" + ? true + : illegalState(`expected '0' or '1' @ pos: ${i}`), + i + 1 + ]; +}; + +const readFloat = (src: string, index: number) => { + index = skipWS(src, index); + let signOk = true; + let dotOk = true; + let expOk = false; + let commaOk = false; + let i = index; + for (let n = src.length; i < n; i++) { + const c = src.charAt(i); + // console.log("float", src.substring(index, i + 1)); + if ("0" <= c && c <= "9") { + expOk = true; + commaOk = true; + signOk = false; + continue; + } + if (c === "-" || c === "+") { + if (!signOk) break; + signOk = false; + continue; + } + if (c === ".") { + if (!dotOk) break; + dotOk = false; + continue; + } + if (c === "e") { + if (!expOk) throw i; + expOk = false; + dotOk = false; + signOk = true; + continue; + } + if (c === ",") { + if (!commaOk) throw i; + i++; + } + break; + } + if (i === index) { + illegalState(`expected coordinate @ pos: ${i}`); + } + return [parseFloat(src.substring(index, i)), i]; +}; diff --git a/packages/geom/src/ctors/path.ts b/packages/geom/src/ctors/path.ts index 56675bcb56..0ddf79c8a9 100644 --- a/packages/geom/src/ctors/path.ts +++ b/packages/geom/src/ctors/path.ts @@ -1,26 +1,11 @@ -import { peek } from "@thi.ng/arrays"; import { isNumber } from "@thi.ng/checks"; import { Attribs, PathSegment, SegmentType } from "@thi.ng/geom-api"; -import { eqDelta, rad } from "@thi.ng/math"; import { map, mapcat } from "@thi.ng/transducers"; -import { - add2, - copy, - maddN2, - mulN2, - set2, - sub2, - Vec, - zeroes -} from "@thi.ng/vectors"; -import { - Cubic, - Line, - Path, - Quadratic -} from "../api"; +import { maddN2, Vec } from "@thi.ng/vectors"; +import { Cubic } from "../api/cubic"; +import { Path } from "../api/path"; import { asCubic } from "../ops/as-cubic"; -import { arcFrom2Points } from "./arc"; +import { PathBuilder } from "./path-builder"; export const path = (segments: PathSegment[], attribs?: Attribs) => new Path(segments, attribs); @@ -71,339 +56,3 @@ export const roundedRect = ( .arcTo([r[0], -r[1]], r, 0, false, true, true) .current(); }; - -export class PathBuilder { - paths: Path[]; - attribs?: Attribs; - protected curr!: Path; - protected currP!: Vec; - protected bezierP!: Vec; - protected startP!: Vec; - - constructor(attribs?: Attribs) { - this.paths = []; - this.attribs = attribs; - this.newPath(); - } - - *[Symbol.iterator]() { - yield* this.paths; - } - - current() { - return this.curr; - } - - newPath() { - this.curr = new Path([], this.attribs); - this.paths.push(this.curr); - this.currP = zeroes(2); - this.bezierP = zeroes(2); - this.startP = zeroes(2); - } - - moveTo(p: Vec, relative = false): PathBuilder { - if (this.curr.segments.length > 0) { - this.curr = new Path(); - this.paths.push(this.curr); - } - p = this.updateCurrent(p, relative); - set2(this.startP, p); - set2(this.bezierP, p); - this.curr.add({ - point: p, - type: SegmentType.MOVE - }); - return this; - } - - lineTo(p: Vec, relative = false): PathBuilder { - this.curr.add({ - geo: new Line([copy(this.currP), this.updateCurrent(p, relative)]), - type: SegmentType.LINE - }); - set2(this.bezierP, this.currP); - return this; - } - - hlineTo(x: number, relative = false): PathBuilder { - const prev = copy(this.currP); - this.currP[0] = relative ? this.currP[0] + x : x; - set2(this.bezierP, this.currP); - this.curr.add({ - geo: new Line([prev, copy(this.currP)]), - type: SegmentType.LINE - }); - return this; - } - - vlineTo(y: number, relative = false): PathBuilder { - const prev = copy(this.currP); - this.currP[1] = relative ? this.currP[1] + y : y; - set2(this.bezierP, this.currP); - this.curr.add({ - geo: new Line([prev, copy(this.currP)]), - type: SegmentType.LINE - }); - return this; - } - - // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Cubic_B%C3%A9zier_Curve - cubicTo(cp1: Vec, cp2: Vec, p: Vec, relative = false) { - const c2 = this.absPoint(cp2, relative); - set2(this.bezierP, c2); - this.curr.add({ - geo: new Cubic([ - copy(this.currP), - this.absPoint(cp1, relative), - c2, - this.updateCurrent(p, relative) - ]), - type: SegmentType.CUBIC - }); - return this; - } - - // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Quadratic_B%C3%A9zier_Curve - quadraticTo(cp: Vec, p: Vec, relative = false) { - const c1 = this.absPoint(cp, relative); - set2(this.bezierP, c1); - this.curr.add({ - geo: new Quadratic([ - copy(this.currP), - c1, - this.updateCurrent(p, relative) - ]), - type: SegmentType.QUADRATIC - }); - return this; - } - - cubicChainTo(cp2: Vec, p: Vec, relative = false) { - const prevMode = peek(this.curr.segments).type; - const c1 = copy(this.currP); - if (prevMode === SegmentType.CUBIC) { - add2(null, sub2([], c1, this.bezierP), c1); - } - const c2 = this.absPoint(cp2, relative); - set2(this.bezierP, c2); - this.curr.add({ - geo: new Cubic([ - copy(this.currP), - c1, - c2, - this.updateCurrent(p, relative) - ]), - type: SegmentType.CUBIC - }); - return this; - } - - quadraticChainTo(p: Vec, relative = false) { - const prevMode = peek(this.curr.segments).type; - const c1 = copy(this.currP); - if (prevMode === SegmentType.QUADRATIC) { - sub2(null, mulN2(null, c1, 2), this.bezierP); - } - set2(this.bezierP, c1); - this.curr.add({ - geo: new Quadratic([ - copy(this.currP), - c1, - this.updateCurrent(p, relative) - ]), - type: SegmentType.CUBIC - }); - return this; - } - - arcTo( - p: Vec, - r: Vec, - xaxis: number, - xl: boolean, - clockwise: boolean, - relative = false - ) { - if (eqDelta(r[0], 0) || eqDelta(r[1], 0)) { - return this.lineTo(p, relative); - } - const prev = copy(this.currP); - this.curr.add({ - geo: arcFrom2Points( - prev, - this.updateCurrent(p, relative), - r, - xaxis, - xl, - clockwise - ), - type: SegmentType.ARC - }); - set2(this.bezierP, this.currP); - return this; - } - - closePath() { - this.curr.add({ - geo: new Line([copy(this.currP), copy(this.startP)]), - type: SegmentType.LINE - }); - this.curr.closed = true; - return this; - } - - protected updateCurrent(p: Vec, relative: boolean) { - p = copy(relative ? add2(null, this.currP, p) : set2(this.currP, p)); - return p; - } - - protected absPoint(p: Vec, relative: boolean) { - return relative ? add2(null, p, this.currP) : p; - } -} - -export const pathBuilder = (attribs?: Attribs) => new PathBuilder(attribs); - -const CMD_RE = /[achlmqstvz]/i; - -export const pathFromSvg = (svg: string) => { - const b = new PathBuilder(); - try { - let cmd = ""; - for (let n = svg.length, i = 0; i < n; ) { - i = skipWS(svg, i); - const c = svg.charAt(i); - if (CMD_RE.test(c)) { - cmd = c; - i++; - } - let p, pa, pb, t1, t2, t3; - switch (cmd.toLowerCase()) { - case "m": - [p, i] = readPoint(svg, i); - b.moveTo(p, cmd === "m"); - break; - case "l": - [p, i] = readPoint(svg, i); - b.lineTo(p, cmd === "l"); - break; - case "h": - [p, i] = readFloat(svg, i); - b.hlineTo(p, cmd === "h"); - break; - case "v": - [p, i] = readFloat(svg, i); - b.vlineTo(p, cmd === "v"); - break; - case "q": - [pa, i] = readPoint(svg, i); - [p, i] = readPoint(svg, i); - // console.log("quadratic", pa.toString(), p.toString()); - b.quadraticTo(pa, p, cmd === "q"); - break; - case "c": - [pa, i] = readPoint(svg, i); - [pb, i] = readPoint(svg, i); - [p, i] = readPoint(svg, i); - // console.log("cubic", pa.toString(), pb.toString(), p.toString()); - b.cubicTo(pa, pb, p, cmd === "c"); - break; - case "s": - [pa, i] = readPoint(svg, i); - [p, i] = readPoint(svg, i); - // console.log("cubicChain", pa.toString(), p.toString()); - b.cubicChainTo(pa, p, cmd === "s"); - break; - case "t": - [p, i] = readPoint(svg, i); - // console.log("quadraticChain", p.toString()); - b.quadraticChainTo(p, cmd === "t"); - break; - case "a": { - [pa, i] = readPoint(svg, i); - [t1, i] = readFloat(svg, i); - [t2, i] = readFloat(svg, i); - [t3, i] = readFloat(svg, i); - [pb, i] = readPoint(svg, i); - // console.log("arc", pa.toString(), rad(t1), t2, t3, pb.toString()); - b.arcTo(pb, pa, rad(t1), !!t2, !!t3, cmd === "a"); - break; - } - case "z": - b.closePath(); - break; - default: - throw new Error( - `unsupported segment type: ${c} @ pos ${i}` - ); - } - } - return b.paths; - } catch (e) { - throw e instanceof Error - ? e - : new Error(`illegal char '${svg.charAt(e)}' @ ${e}`); - } -}; - -const readPoint = (src: string, index: number): [Vec, number] => { - let x, y; - [x, index] = readFloat(src, index); - index = skipWS(src, index); - [y, index] = readFloat(src, index); - return [[x, y], index]; -}; - -const isWS = (c: string) => c === " " || c === "\n" || c === "\r" || c === "\t"; - -const skipWS = (src: string, i: number) => { - const n = src.length; - while (i < n && isWS(src.charAt(i))) i++; - return i; -}; - -const readFloat = (src: string, index: number) => { - index = skipWS(src, index); - let signOk = true; - let dotOk = true; - let expOk = false; - let commaOk = false; - let i = index; - for (let n = src.length; i < n; i++) { - const c = src.charAt(i); - // console.log("float", src.substring(index, i + 1)); - if ("0" <= c && c <= "9") { - expOk = true; - commaOk = true; - signOk = false; - continue; - } - if (c === "-" || c === "+") { - if (!signOk) break; - signOk = false; - continue; - } - if (c === ".") { - if (!dotOk) break; - dotOk = false; - continue; - } - if (c === "e") { - if (!expOk) throw i; - expOk = false; - dotOk = false; - signOk = true; - continue; - } - if (c === ",") { - if (!commaOk) throw i; - i++; - } - break; - } - if (i === index) { - throw new Error(`expected coordinate @ pos: ${i}`); - } - return [parseFloat(src.substring(index, i)), i]; -}; diff --git a/packages/geom/src/ctors/plane.ts b/packages/geom/src/ctors/plane.ts index 3c9797afae..5f208ca27d 100644 --- a/packages/geom/src/ctors/plane.ts +++ b/packages/geom/src/ctors/plane.ts @@ -6,7 +6,7 @@ import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { Plane } from "../api"; +import { Plane } from "../api/plane"; export const plane = (normal: Vec, w: number, attribs?: Attribs) => new Plane(normalize(null, normal), w, attribs); diff --git a/packages/geom/src/ctors/points.ts b/packages/geom/src/ctors/points.ts index 576ca2c876..5f696aee71 100644 --- a/packages/geom/src/ctors/points.ts +++ b/packages/geom/src/ctors/points.ts @@ -1,6 +1,6 @@ import { Attribs } from "@thi.ng/geom-api"; import { Vec } from "@thi.ng/vectors"; -import { Points } from "../api"; +import { Points } from "../api/points"; export const points = (pts?: Vec[], attribs?: Attribs) => new Points(pts, attribs); diff --git a/packages/geom/src/ctors/polygon.ts b/packages/geom/src/ctors/polygon.ts index c4695aa2bb..ec2765e6b0 100644 --- a/packages/geom/src/ctors/polygon.ts +++ b/packages/geom/src/ctors/polygon.ts @@ -9,7 +9,7 @@ import { zip } from "@thi.ng/transducers"; import { cartesian2, Vec } from "@thi.ng/vectors"; -import { Polygon } from "../api"; +import { Polygon } from "../api/polygon"; export const polygon = (pts: Vec[], attribs?: Attribs) => new Polygon(pts, attribs); diff --git a/packages/geom/src/ctors/polyline.ts b/packages/geom/src/ctors/polyline.ts index e6cb9e2864..9d157095bb 100644 --- a/packages/geom/src/ctors/polyline.ts +++ b/packages/geom/src/ctors/polyline.ts @@ -1,6 +1,6 @@ import { Attribs } from "@thi.ng/geom-api"; import { Vec } from "@thi.ng/vectors"; -import { Polyline } from "../api"; +import { Polyline } from "../api/polyline"; export const polyline = (pts: Vec[], attribs?: Attribs) => new Polyline(pts, attribs); diff --git a/packages/geom/src/ctors/quad.ts b/packages/geom/src/ctors/quad.ts index b1da3db85b..6113630d01 100644 --- a/packages/geom/src/ctors/quad.ts +++ b/packages/geom/src/ctors/quad.ts @@ -8,14 +8,16 @@ import { Vec, Z3 } from "@thi.ng/vectors"; -import { Plane, Quad, Quad3 } from "../api"; +import { Plane } from "../api/plane"; +import { Quad } from "../api/quad"; +import { Quad3 } from "../api/quad3"; import { argAttribs } from "../internal/args"; +import { pclike } from "../internal/pclike"; export function quad(a: Vec, b: Vec, c: Vec, d: Vec, attribs?: Attribs): Quad; export function quad(pts: Vec[], attribs?: Attribs): Quad; export function quad(...args: any[]) { - const attr = argAttribs(args); - return new Quad(args.length === 1 ? args[0] : args, attr); + return pclike(Quad, args); } export function quad3(a: Vec, b: Vec, c: Vec, d: Vec, attribs?: Attribs): Quad; diff --git a/packages/geom/src/ctors/quadratic.ts b/packages/geom/src/ctors/quadratic.ts index 300522db80..4b5086c7c6 100644 --- a/packages/geom/src/ctors/quadratic.ts +++ b/packages/geom/src/ctors/quadratic.ts @@ -1,14 +1,13 @@ import { Attribs } from "@thi.ng/geom-api"; import { quadraticFromLine as _line } from "@thi.ng/geom-splines"; import { Vec } from "@thi.ng/vectors"; -import { Quadratic } from "../api"; -import { argAttribs } from "../internal/args"; +import { Quadratic } from "../api/quadratic"; +import { pclike } from "../internal/pclike"; export function quadratic(a: Vec, b: Vec, c: Vec, attribs?: Attribs): Quadratic; export function quadratic(pts: Vec[], attribs?: Attribs): Quadratic; export function quadratic(...args: any[]) { - const attr = argAttribs(args); - return new Quadratic(args.length === 1 ? args[0] : args, attr); + return pclike(Quadratic, args); } export const quadraticFromLine = (a: Vec, b: Vec, attribs?: Attribs) => diff --git a/packages/geom/src/ctors/ray.ts b/packages/geom/src/ctors/ray.ts index b96669d5d6..c98cc9ded7 100644 --- a/packages/geom/src/ctors/ray.ts +++ b/packages/geom/src/ctors/ray.ts @@ -1,6 +1,6 @@ import { Attribs } from "@thi.ng/geom-api"; import { normalize as _norm, Vec } from "@thi.ng/vectors"; -import { Ray } from "../api"; +import { Ray } from "../api/ray"; export const ray = (pos: Vec, dir: Vec, attribs?: Attribs, normalize = true) => new Ray(pos, normalize ? _norm(null, dir) : dir, attribs); diff --git a/packages/geom/src/ctors/rect.ts b/packages/geom/src/ctors/rect.ts index a34e345b83..da813eb2e1 100644 --- a/packages/geom/src/ctors/rect.ts +++ b/packages/geom/src/ctors/rect.ts @@ -8,7 +8,9 @@ import { subN2, Vec } from "@thi.ng/vectors"; -import { Circle, Polygon, Rect } from "../api"; +import { Circle } from "../api/circle"; +import { Polygon } from "../api/polygon"; +import { Rect } from "../api/rect"; import { argsVV } from "../internal/args"; export function rect(pos: Vec, size: number | Vec, attribs?: Attribs): Rect; diff --git a/packages/geom/src/ctors/sphere.ts b/packages/geom/src/ctors/sphere.ts index b855a98067..c22c30d4ec 100644 --- a/packages/geom/src/ctors/sphere.ts +++ b/packages/geom/src/ctors/sphere.ts @@ -5,7 +5,7 @@ import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { Sphere } from "../api"; +import { Sphere } from "../api/sphere"; import { argsVN } from "../internal/args"; export function sphere(pos: Vec, r: number, attribs?: Attribs): Sphere; diff --git a/packages/geom/src/ctors/triangle.ts b/packages/geom/src/ctors/triangle.ts index 85daefc683..bdfed12bee 100644 --- a/packages/geom/src/ctors/triangle.ts +++ b/packages/geom/src/ctors/triangle.ts @@ -1,14 +1,13 @@ import { Attribs } from "@thi.ng/geom-api"; import { equilateralTriangle2 } from "@thi.ng/geom-poly-utils"; import { Vec } from "@thi.ng/vectors"; -import { Triangle } from "../api"; -import { argAttribs } from "../internal/args"; +import { Triangle } from "../api/triangle"; +import { pclike } from "../internal/pclike"; export function triangle(a: Vec, b: Vec, c: Vec, attribs?: Attribs): Triangle; export function triangle(pts: Vec[], attribs?: Attribs): Triangle; export function triangle(...args: any[]) { - const attr = argAttribs(args); - return new Triangle(args.length === 1 ? args[0] : args, attr); + return pclike(Triangle, args); } export const equilateralTriangle = (a: Vec, b: Vec, attribs?: Attribs) => diff --git a/packages/geom/src/index.ts b/packages/geom/src/index.ts index 7e9536b6ed..d36ccba5a6 100644 --- a/packages/geom/src/index.ts +++ b/packages/geom/src/index.ts @@ -1,4 +1,23 @@ -export * from "./api"; +export * from "./api/aabb"; +export * from "./api/apc"; +export * from "./api/arc"; +export * from "./api/circle"; +export * from "./api/cubic"; +export * from "./api/ellipse"; +export * from "./api/group"; +export * from "./api/line"; +export * from "./api/path"; +export * from "./api/plane"; +export * from "./api/points"; +export * from "./api/polygon"; +export * from "./api/polyline"; +export * from "./api/quad"; +export * from "./api/quad3"; +export * from "./api/quadratic"; +export * from "./api/ray"; +export * from "./api/rect"; +export * from "./api/sphere"; +export * from "./api/triangle"; export * from "./ctors/aabb"; export * from "./ctors/arc"; @@ -8,6 +27,8 @@ export * from "./ctors/ellipse"; export * from "./ctors/group"; export * from "./ctors/line"; export * from "./ctors/path"; +export * from "./ctors/path-builder"; +export * from "./ctors/path-from-svg"; export * from "./ctors/plane"; export * from "./ctors/points"; export * from "./ctors/polygon"; @@ -57,7 +78,11 @@ export * from "./ops/warp-points"; export * from "./ops/with-attribs"; export * from "./internal/coll-bounds"; +export * from "./internal/copy-attribs"; +export * from "./internal/copy-shape"; export * from "./internal/edges"; +export * from "./internal/pclike"; +export * from "./internal/points-as-shape"; export * from "./internal/split"; export * from "./internal/transform-points"; export * from "./internal/translate-points"; diff --git a/packages/geom/src/internal/copy-attribs.ts b/packages/geom/src/internal/copy-attribs.ts new file mode 100644 index 0000000000..8ebdd1df15 --- /dev/null +++ b/packages/geom/src/internal/copy-attribs.ts @@ -0,0 +1,3 @@ +import { Attribs, IShape } from "@thi.ng/geom-api"; + +export const copyAttribs = ($: IShape) => { ...$.attribs }; diff --git a/packages/geom/src/internal/copy-shape.ts b/packages/geom/src/internal/copy-shape.ts new file mode 100644 index 0000000000..c0f47a7416 --- /dev/null +++ b/packages/geom/src/internal/copy-shape.ts @@ -0,0 +1,6 @@ +import { PCLike, PCLikeConstructor } from "@thi.ng/geom-api"; +import { copyVectors } from "@thi.ng/vectors"; +import { copyAttribs } from "./copy-attribs"; + +export const copyShape = (ctor: PCLikeConstructor, inst: PCLike) => + new ctor(copyVectors(inst.points), copyAttribs(inst)); diff --git a/packages/geom/src/internal/edges.ts b/packages/geom/src/internal/edges.ts index ea39aaa5f1..b1e0e5e9be 100644 --- a/packages/geom/src/internal/edges.ts +++ b/packages/geom/src/internal/edges.ts @@ -1,7 +1,7 @@ -import { partition, wrap } from "@thi.ng/transducers"; +import { partition, wrapSides } from "@thi.ng/transducers"; import { ReadonlyVec, VecPair } from "@thi.ng/vectors"; export const edgeIterator = (vertices: Iterable, closed = false) => >( - partition(2, 1, closed ? wrap(vertices, 1, false, true) : vertices) + partition(2, 1, closed ? wrapSides(vertices, 0, 1) : vertices) ); diff --git a/packages/geom/src/internal/pclike.ts b/packages/geom/src/internal/pclike.ts new file mode 100644 index 0000000000..352ca06449 --- /dev/null +++ b/packages/geom/src/internal/pclike.ts @@ -0,0 +1,7 @@ +import { PCLikeConstructor } from "@thi.ng/geom-api"; +import { argAttribs } from "./args"; + +export const pclike = (ctor: PCLikeConstructor, args: any[]) => { + const attr = argAttribs(args); + return new ctor(args.length === 1 ? args[0] : args, attr); +}; diff --git a/packages/geom/src/internal/points-as-shape.ts b/packages/geom/src/internal/points-as-shape.ts new file mode 100644 index 0000000000..789f2d902e --- /dev/null +++ b/packages/geom/src/internal/points-as-shape.ts @@ -0,0 +1,12 @@ +import { Attribs, PCLikeConstructor } from "@thi.ng/geom-api"; +import { map } from "@thi.ng/transducers"; +import { copyVectors, Vec } from "@thi.ng/vectors"; + +export const pointArraysAsShapes = ( + ctor: PCLikeConstructor, + src?: Iterable, + attribs?: Attribs +) => + src + ? [...map((pts) => new ctor(copyVectors(pts), { ...attribs }), src)] + : undefined; diff --git a/packages/geom/src/internal/transform-points.ts b/packages/geom/src/internal/transform-points.ts index c11c93b361..58c2376a3e 100644 --- a/packages/geom/src/internal/transform-points.ts +++ b/packages/geom/src/internal/transform-points.ts @@ -1,5 +1,7 @@ +import { PCLike, PCLikeConstructor } from "@thi.ng/geom-api"; import { mulV, ReadonlyMat } from "@thi.ng/matrices"; import { ReadonlyVec } from "@thi.ng/vectors"; +import { copyAttribs } from "./copy-attribs"; export const transformPoints = (pts: ReadonlyVec[], mat: ReadonlyMat) => ( pts.forEach((p) => mulV(null, mat, p)), pts @@ -7,3 +9,8 @@ export const transformPoints = (pts: ReadonlyVec[], mat: ReadonlyMat) => ( export const transformedPoints = (pts: ReadonlyVec[], mat: ReadonlyMat) => pts.map((p) => mulV([], mat, p)); + +export const transformedShape = (ctor: PCLikeConstructor) => ( + $: PCLike, + mat: ReadonlyMat +) => new ctor(transformedPoints($.points, mat), copyAttribs($)); diff --git a/packages/geom/src/internal/translate-points.ts b/packages/geom/src/internal/translate-points.ts index db46bcd8fe..d7e1d58179 100644 --- a/packages/geom/src/internal/translate-points.ts +++ b/packages/geom/src/internal/translate-points.ts @@ -1,4 +1,11 @@ +import { PCLike, PCLikeConstructor } from "@thi.ng/geom-api"; import { add, ReadonlyVec } from "@thi.ng/vectors"; +import { copyAttribs } from "./copy-attribs"; export const translatedPoints = (pts: ReadonlyVec[], delta: ReadonlyVec) => pts.map((x) => add([], x, delta)); + +export const translatedShape = (ctor: PCLikeConstructor) => ( + $: PCLike, + mat: ReadonlyVec +) => new ctor(translatedPoints($.points, mat), copyAttribs($)); diff --git a/packages/geom/src/ops/arc-length.ts b/packages/geom/src/ops/arc-length.ts index 9457b5d44e..842cde242c 100644 --- a/packages/geom/src/ops/arc-length.ts +++ b/packages/geom/src/ops/arc-length.ts @@ -4,15 +4,13 @@ import { IShape, Type } from "@thi.ng/geom-api"; import { perimeter } from "@thi.ng/geom-poly-utils"; import { PI, TAU } from "@thi.ng/math"; import { dist } from "@thi.ng/vectors"; -import { - Circle, - Ellipse, - Group, - Line, - Polygon, - Rect, - Triangle -} from "../api"; +import { Circle } from "../api/circle"; +import { Ellipse } from "../api/ellipse"; +import { Group } from "../api/group"; +import { Line } from "../api/line"; +import { Polygon } from "../api/polygon"; +import { Rect } from "../api/rect"; +import { Triangle } from "../api/triangle"; import { dispatch } from "../internal/dispatch"; /** diff --git a/packages/geom/src/ops/area.ts b/packages/geom/src/ops/area.ts index 0f01b6057c..83b1ed3e82 100644 --- a/packages/geom/src/ops/area.ts +++ b/packages/geom/src/ops/area.ts @@ -4,17 +4,15 @@ import { IShape, Type } from "@thi.ng/geom-api"; import { polyArea2 } from "@thi.ng/geom-poly-utils"; import { PI } from "@thi.ng/math"; import { signedArea2, Vec } from "@thi.ng/vectors"; -import { - AABB, - Arc, - Circle, - Ellipse, - Group, - Polygon, - Rect, - Sphere, - Triangle -} from "../api"; +import { AABB } from "../api/aabb"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Ellipse } from "../api/ellipse"; +import { Group } from "../api/group"; +import { Polygon } from "../api/polygon"; +import { Rect } from "../api/rect"; +import { Sphere } from "../api/sphere"; +import { Triangle } from "../api/triangle"; import { dispatch } from "../internal/dispatch"; /** diff --git a/packages/geom/src/ops/as-cubic.ts b/packages/geom/src/ops/as-cubic.ts index 3628d913db..b34fbbb6a1 100644 --- a/packages/geom/src/ops/as-cubic.ts +++ b/packages/geom/src/ops/as-cubic.ts @@ -1,22 +1,20 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation1, MultiFn1O } from "@thi.ng/defmulti"; import { CubicOpts, IShape, Type } from "@thi.ng/geom-api"; -import { closedCubicFromBreakPoints, closedCubicFromControlPoints, cubicFromArc } from "@thi.ng/geom-splines"; +import { closedCubicFromBreakPoints, closedCubicFromControlPoints } from "@thi.ng/geom-splines"; import { TAU } from "@thi.ng/math"; import { mapcat } from "@thi.ng/transducers"; -import { - Arc, - Circle, - Cubic, - Group, - Line, - Path, - Polygon, - Quadratic, - Rect -} from "../api"; +import { Circle } from "../api/circle"; +import { Cubic } from "../api/cubic"; +import { Group } from "../api/group"; +import { Line } from "../api/line"; +import { Path } from "../api/path"; +import { Polygon } from "../api/polygon"; +import { Quadratic } from "../api/quadratic"; +import { Rect } from "../api/rect"; import { arc } from "../ctors/arc"; -import { cubicFromLine, cubicFromQuadratic } from "../ctors/cubic"; +import { cubicFromArc, cubicFromLine, cubicFromQuadratic } from "../ctors/cubic"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { asPolygon } from "./as-polygon"; @@ -25,10 +23,7 @@ export const asCubic: MultiFn1O, Cubic[]> = defmulti( ); asCubic.addAll(>>{ - [Type.ARC]: ($: Arc) => - cubicFromArc($.pos, $.r, $.axis, $.start, $.end).map( - (c) => new Cubic(c, $.attribs) - ), + [Type.ARC]: cubicFromArc, [Type.CIRCLE]: ($: Circle) => asCubic(arc($.pos, $.r, 0, 0, TAU, true, true)), @@ -50,7 +45,7 @@ asCubic.addAll(>>{ return (opts.breakPoints ? closedCubicFromBreakPoints($.points, opts.scale, opts.uniform) : closedCubicFromControlPoints($.points, opts.scale, opts.uniform) - ).map((pts) => new Cubic(pts, { ...$.attribs })); + ).map((pts) => new Cubic(pts, copyAttribs($))); }, [Type.QUADRATIC]: ({ attribs, points }: Quadratic) => [ diff --git a/packages/geom/src/ops/as-path.ts b/packages/geom/src/ops/as-path.ts index 57a2db7105..66cfaec1b1 100644 --- a/packages/geom/src/ops/as-path.ts +++ b/packages/geom/src/ops/as-path.ts @@ -1,6 +1,7 @@ import { Attribs, IShape } from "@thi.ng/geom-api"; import { pathFromCubics } from "../ctors/path"; +import { copyAttribs } from "../internal/copy-attribs"; import { asCubic } from "./as-cubic"; export const asPath = (src: IShape, attribs?: Attribs) => - pathFromCubics(asCubic(src), attribs); + pathFromCubics(asCubic(src), attribs || copyAttribs(src)); diff --git a/packages/geom/src/ops/as-polygon.ts b/packages/geom/src/ops/as-polygon.ts index f96e89c1d0..8fb8d66a35 100644 --- a/packages/geom/src/ops/as-polygon.ts +++ b/packages/geom/src/ops/as-polygon.ts @@ -1,6 +1,7 @@ import { defmulti, MultiFn1O } from "@thi.ng/defmulti"; import { IShape, SamplingOpts, Type } from "@thi.ng/geom-api"; -import { Polygon } from "../api"; +import { Polygon } from "../api/polygon"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { vertices } from "./vertices"; @@ -11,7 +12,7 @@ export const asPolygon: MultiFn1O< > = defmulti(dispatch); asPolygon.addAll({ - [Type.POINTS]: ($, opts) => new Polygon(vertices($, opts), { ...$.attribs }) + [Type.POINTS]: ($, opts) => new Polygon(vertices($, opts), copyAttribs($)) }); asPolygon.isa(Type.CIRCLE, Type.POINTS); diff --git a/packages/geom/src/ops/as-polyline.ts b/packages/geom/src/ops/as-polyline.ts index d899688242..4926ae2d5f 100644 --- a/packages/geom/src/ops/as-polyline.ts +++ b/packages/geom/src/ops/as-polyline.ts @@ -1,7 +1,9 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation1O, MultiFn1O } from "@thi.ng/defmulti"; import { IShape, SamplingOpts, Type } from "@thi.ng/geom-api"; -import { Path, Polyline } from "../api"; +import { Path } from "../api/path"; +import { Polyline } from "../api/polyline"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { vertices } from "./vertices"; @@ -17,18 +19,19 @@ asPolyline.addAll(< > >{ [Type.POINTS]: ($: IShape, opts) => - new Polyline(vertices($, opts), { ...$.attribs }), + new Polyline(vertices($, opts), copyAttribs($)), [Type.PATH]: ($: Path, opts) => { const pts = vertices($, opts); - return new Polyline($.closed ? pts.concat([pts[0]]) : pts, { - ...$.attribs - }); + return new Polyline( + $.closed ? pts.concat([pts[0]]) : pts, + copyAttribs($) + ); }, [Type.POLYGON]: ($: IShape, opts) => { const pts = vertices($, opts); - return new Polyline(pts.concat([pts[0]]), { ...$.attribs }); + return new Polyline(pts.concat([pts[0]]), copyAttribs($)); } }); diff --git a/packages/geom/src/ops/bounds.ts b/packages/geom/src/ops/bounds.ts index 8bc06e8653..d14d29b576 100644 --- a/packages/geom/src/ops/bounds.ts +++ b/packages/geom/src/ops/bounds.ts @@ -27,17 +27,15 @@ import { sub2, subN2 } from "@thi.ng/vectors"; -import { - Arc, - Circle, - Cubic, - Ellipse, - Group, - Line, - Path, - Quadratic, - Rect -} from "../api"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Cubic } from "../api/cubic"; +import { Ellipse } from "../api/ellipse"; +import { Group } from "../api/group"; +import { Line } from "../api/line"; +import { Path } from "../api/path"; +import { Quadratic } from "../api/quadratic"; +import { Rect } from "../api/rect"; import { rectFromMinMax } from "../ctors/rect"; import { collBounds } from "../internal/coll-bounds"; import { dispatch } from "../internal/dispatch"; diff --git a/packages/geom/src/ops/center.ts b/packages/geom/src/ops/center.ts index 5323b62c21..8711f8a59b 100644 --- a/packages/geom/src/ops/center.ts +++ b/packages/geom/src/ops/center.ts @@ -14,12 +14,11 @@ import { ZERO2, ZERO3 } from "@thi.ng/vectors"; -import { - Arc, - Circle, - Ellipse, - Sphere -} from "../api"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Ellipse } from "../api/ellipse"; +import { Sphere } from "../api/sphere"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { centroid } from "./centroid"; import { translate } from "./translate"; @@ -47,15 +46,15 @@ center.addAll(< $.end, $.xl, $.cw, - { ...$.attribs } + copyAttribs($) ), [Type.CIRCLE]: ($: Circle, origin = ZERO2) => - new Circle(set2([], origin), $.r, { ...$.attribs }), + new Circle(set2([], origin), $.r, copyAttribs($)), [Type.ELLIPSE]: ($: Ellipse, origin = ZERO2) => - new Ellipse(set2([], origin), set2([], $.r), { ...$.attribs }), + new Ellipse(set2([], origin), set2([], $.r), copyAttribs($)), [Type.SPHERE]: ($: Sphere, origin = ZERO3) => - new Sphere(set3([], origin), $.r, { ...$.attribs }) + new Sphere(set3([], origin), $.r, copyAttribs($)) }); diff --git a/packages/geom/src/ops/centroid.ts b/packages/geom/src/ops/centroid.ts index 3461c8c286..d07a5eb776 100644 --- a/packages/geom/src/ops/centroid.ts +++ b/packages/geom/src/ops/centroid.ts @@ -16,14 +16,12 @@ import { set, Vec } from "@thi.ng/vectors"; -import { - Circle, - Group, - Line, - Plane, - Polygon, - Triangle -} from "../api"; +import { Circle } from "../api/circle"; +import { Group } from "../api/group"; +import { Line } from "../api/line"; +import { Plane } from "../api/plane"; +import { Polygon } from "../api/polygon"; +import { Triangle } from "../api/triangle"; import { dispatch } from "../internal/dispatch"; import { bounds } from "./bounds"; diff --git a/packages/geom/src/ops/classify-point.ts b/packages/geom/src/ops/classify-point.ts index 6feabcb8de..1e38b23af7 100644 --- a/packages/geom/src/ops/classify-point.ts +++ b/packages/geom/src/ops/classify-point.ts @@ -4,7 +4,9 @@ import { IShape, Type } from "@thi.ng/geom-api"; import { classifyPointInCircle, classifyPointInTriangle2 } from "@thi.ng/geom-isec"; import { EPS, sign } from "@thi.ng/math"; import { dot, ReadonlyVec } from "@thi.ng/vectors"; -import { Circle, Plane, Triangle } from "../api"; +import { Circle } from "../api/circle"; +import { Plane } from "../api/plane"; +import { Triangle } from "../api/triangle"; import { dispatch } from "../internal/dispatch"; export const classifyPoint: MultiFn2O< diff --git a/packages/geom/src/ops/clip-convex.ts b/packages/geom/src/ops/clip-convex.ts index 598418c593..310fcfbb18 100644 --- a/packages/geom/src/ops/clip-convex.ts +++ b/packages/geom/src/ops/clip-convex.ts @@ -2,7 +2,8 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation2 } from "@thi.ng/defmulti"; import { IShape, Type } from "@thi.ng/geom-api"; import { sutherlandHodgeman } from "@thi.ng/geom-clip"; -import { Polygon } from "../api"; +import { Polygon } from "../api/polygon"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { centroid } from "./centroid"; import { vertices } from "./vertices"; @@ -17,7 +18,7 @@ clipConvex.addAll(>>{ vertices(boundary), centroid(boundary) ), - { ...$.attribs } + copyAttribs($) ), [Type.RECT]: ($: IShape, boundary: IShape) => @@ -27,7 +28,7 @@ clipConvex.addAll(>>{ vertices(boundary), centroid(boundary) ), - { ...$.attribs } + copyAttribs($) ) }); diff --git a/packages/geom/src/ops/closest-point.ts b/packages/geom/src/ops/closest-point.ts index 8ea2e755b5..5f70be2dbb 100644 --- a/packages/geom/src/ops/closest-point.ts +++ b/packages/geom/src/ops/closest-point.ts @@ -18,16 +18,14 @@ import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { - AABB, - Arc, - Circle, - Cubic, - Line, - Plane, - Quadratic, - Rect -} from "../api"; +import { AABB } from "../api/aabb"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Cubic } from "../api/cubic"; +import { Line } from "../api/line"; +import { Plane } from "../api/plane"; +import { Quadratic } from "../api/quadratic"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; export const closestPoint: MultiFn2O< diff --git a/packages/geom/src/ops/convex-hull.ts b/packages/geom/src/ops/convex-hull.ts index 033876be71..39c76add56 100644 --- a/packages/geom/src/ops/convex-hull.ts +++ b/packages/geom/src/ops/convex-hull.ts @@ -2,17 +2,18 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation1 } from "@thi.ng/defmulti"; import { IShape, PCLike, Type } from "@thi.ng/geom-api"; import { grahamScan2 } from "@thi.ng/geom-hull"; -import { Polygon } from "../api"; +import { Polygon } from "../api/polygon"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { vertices } from "./vertices"; export const convexHull = defmulti(dispatch); convexHull.addAll(>>{ - [Type.GROUP]: ($: IShape) => new Polygon(vertices($), { ...$.attribs }), + [Type.GROUP]: ($: IShape) => new Polygon(vertices($), copyAttribs($)), [Type.POINTS]: ($: PCLike) => - new Polygon(grahamScan2($.points), { ...$.attribs }), + new Polygon(grahamScan2($.points), copyAttribs($)), [Type.TRIANGLE]: ($: IShape) => $.copy() }); diff --git a/packages/geom/src/ops/edges.ts b/packages/geom/src/ops/edges.ts index 1de8805af5..d097bc2f72 100644 --- a/packages/geom/src/ops/edges.ts +++ b/packages/geom/src/ops/edges.ts @@ -2,7 +2,9 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation1O, MultiFn1O } from "@thi.ng/defmulti"; import { IShape, SamplingOpts, Type } from "@thi.ng/geom-api"; import { VecPair } from "@thi.ng/vectors"; -import { Polygon, Polyline, Rect } from "../api"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; import { edgeIterator } from "../internal/edges"; import { vertices } from "./vertices"; diff --git a/packages/geom/src/ops/fit-into-bounds.ts b/packages/geom/src/ops/fit-into-bounds.ts index a332107176..c910e63ce6 100644 --- a/packages/geom/src/ops/fit-into-bounds.ts +++ b/packages/geom/src/ops/fit-into-bounds.ts @@ -11,7 +11,7 @@ import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { Rect } from "../api"; +import { Rect } from "../api/rect"; import { collBounds } from "../internal/coll-bounds"; import { bounds } from "./bounds"; import { center } from "./center"; diff --git a/packages/geom/src/ops/flip.ts b/packages/geom/src/ops/flip.ts index e2647baf58..86285478ac 100644 --- a/packages/geom/src/ops/flip.ts +++ b/packages/geom/src/ops/flip.ts @@ -2,12 +2,10 @@ import { IObjectOf } from "@thi.ng/api"; import { DEFAULT, defmulti, Implementation1 } from "@thi.ng/defmulti"; import { IShape, PCLike, Type } from "@thi.ng/geom-api"; import { neg } from "@thi.ng/vectors"; -import { - Arc, - Group, - Path, - Ray -} from "../api"; +import { Arc } from "../api/arc"; +import { Group } from "../api/group"; +import { Path } from "../api/path"; +import { Ray } from "../api/ray"; import { dispatch } from "../internal/dispatch"; export const flip = defmulti(dispatch); diff --git a/packages/geom/src/ops/intersects.ts b/packages/geom/src/ops/intersects.ts index 0503002c9c..b8575e64cd 100644 --- a/packages/geom/src/ops/intersects.ts +++ b/packages/geom/src/ops/intersects.ts @@ -19,15 +19,13 @@ import { testRectCircle, testRectRect } from "@thi.ng/geom-isec"; -import { - AABB, - Circle, - Line, - Plane, - Ray, - Rect, - Sphere -} from "../api"; +import { AABB } from "../api/aabb"; +import { Circle } from "../api/circle"; +import { Line } from "../api/line"; +import { Plane } from "../api/plane"; +import { Ray } from "../api/ray"; +import { Rect } from "../api/rect"; +import { Sphere } from "../api/sphere"; import { dispatch2 } from "../internal/dispatch"; export const intersects: MultiFn2O< diff --git a/packages/geom/src/ops/map-point.ts b/packages/geom/src/ops/map-point.ts index 3142fe47bb..34e78cfa29 100644 --- a/packages/geom/src/ops/map-point.ts +++ b/packages/geom/src/ops/map-point.ts @@ -7,7 +7,7 @@ import { sub, Vec } from "@thi.ng/vectors"; -import { Rect } from "../api"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; export const mapPoint: MultiFn2O = defmulti(< diff --git a/packages/geom/src/ops/point-at.ts b/packages/geom/src/ops/point-at.ts index f5266937a0..6af2982479 100644 --- a/packages/geom/src/ops/point-at.ts +++ b/packages/geom/src/ops/point-at.ts @@ -12,17 +12,15 @@ import { mixQuadratic, Vec } from "@thi.ng/vectors"; -import { - Arc, - Circle, - Cubic, - Ellipse, - Line, - Polygon, - Quadratic, - Ray, - Rect -} from "../api"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Cubic } from "../api/cubic"; +import { Ellipse } from "../api/ellipse"; +import { Line } from "../api/line"; +import { Polygon } from "../api/polygon"; +import { Quadratic } from "../api/quadratic"; +import { Ray } from "../api/ray"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; import { vertices } from "./vertices"; diff --git a/packages/geom/src/ops/point-inside.ts b/packages/geom/src/ops/point-inside.ts index bbe7fc230c..35e24bd6e6 100644 --- a/packages/geom/src/ops/point-inside.ts +++ b/packages/geom/src/ops/point-inside.ts @@ -10,15 +10,13 @@ import { pointInTriangle2 } from "@thi.ng/geom-isec"; import { isInArray, ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { - AABB, - Circle, - Line, - Points, - Polygon, - Rect, - Triangle -} from "../api"; +import { AABB } from "../api/aabb"; +import { Circle } from "../api/circle"; +import { Line } from "../api/line"; +import { Points } from "../api/points"; +import { Polygon } from "../api/polygon"; +import { Rect } from "../api/rect"; +import { Triangle } from "../api/triangle"; import { dispatch } from "../internal/dispatch"; export const pointInside = defmulti(dispatch); diff --git a/packages/geom/src/ops/resample.ts b/packages/geom/src/ops/resample.ts index aedb27815c..2be7f2d068 100644 --- a/packages/geom/src/ops/resample.ts +++ b/packages/geom/src/ops/resample.ts @@ -7,7 +7,9 @@ import { Type } from "@thi.ng/geom-api"; import { resample as _resample } from "@thi.ng/geom-resample"; -import { Polygon, Polyline } from "../api"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { asPolygon } from "./as-polygon"; @@ -23,10 +25,10 @@ resample.addAll(< [Type.CIRCLE]: ($: IShape, opts) => asPolygon($, opts), [Type.POLYGON]: ($: PCLike, opts) => - new Polygon(_resample($.points, opts, true, true), { ...$.attribs }), + new Polygon(_resample($.points, opts, true, true), copyAttribs($)), [Type.POLYLINE]: ($: PCLike, opts) => - new Polyline(_resample($.points, opts, false, true), { ...$.attribs }) + new Polyline(_resample($.points, opts, false, true), copyAttribs($)) }); resample.isa(Type.ELLIPSE, Type.CIRCLE); diff --git a/packages/geom/src/ops/simplify.ts b/packages/geom/src/ops/simplify.ts index aff8d10b85..4434eaa28a 100644 --- a/packages/geom/src/ops/simplify.ts +++ b/packages/geom/src/ops/simplify.ts @@ -9,16 +9,19 @@ import { } from "@thi.ng/geom-api"; import { simplify as _simplify } from "@thi.ng/geom-resample"; import { Vec } from "@thi.ng/vectors"; -import { Path, Polygon, Polyline } from "../api"; +import { Path } from "../api/path"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; import { vertices } from "./vertices"; export const simplify = defmulti(dispatch); simplify.addAll(>>{ - [Type.PATH]: (path: Path, eps = 0.1) => { + [Type.PATH]: ($: Path, eps = 0.1) => { const res: PathSegment[] = []; - const orig = path.segments; + const orig = $.segments; const n = orig.length; let points!: Vec[] | null; let lastP!: Vec; @@ -50,12 +53,12 @@ simplify.addAll(>>{ type: SegmentType.POLYLINE }); } - return new Path(res, { ...path.attribs }); + return new Path(res, copyAttribs($)); }, - [Type.POLYGON]: (poly: Polygon, eps = 0.1) => - new Polygon(_simplify(poly.points, eps, true), { ...poly.attribs }), + [Type.POLYGON]: ($: Polygon, eps = 0.1) => + new Polygon(_simplify($.points, eps, true), copyAttribs($)), - [Type.POLYLINE]: (poly: Polyline, eps = 0.1) => - new Polyline(_simplify(poly.points, eps), { ...poly.attribs }) + [Type.POLYLINE]: ($: Polyline, eps = 0.1) => + new Polyline(_simplify($.points, eps), copyAttribs($)) }); diff --git a/packages/geom/src/ops/split-at.ts b/packages/geom/src/ops/split-at.ts index 801fe4efc4..d416e5ec01 100644 --- a/packages/geom/src/ops/split-at.ts +++ b/packages/geom/src/ops/split-at.ts @@ -4,15 +4,15 @@ import { IShape, Type } from "@thi.ng/geom-api"; import { Sampler } from "@thi.ng/geom-resample"; import { cubicSplitAt, quadraticSplitAt } from "@thi.ng/geom-splines"; import { fit01 } from "@thi.ng/math"; -import { copyVectors, set } from "@thi.ng/vectors"; -import { - Arc, - Cubic, - Line, - Polyline, - Quadratic -} from "../api"; +import { set } from "@thi.ng/vectors"; +import { Arc } from "../api/arc"; +import { Cubic } from "../api/cubic"; +import { Line } from "../api/line"; +import { Polyline } from "../api/polyline"; +import { Quadratic } from "../api/quadratic"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; +import { pointArraysAsShapes } from "../internal/points-as-shape"; import { splitLine } from "../internal/split"; export const splitAt = defmulti(dispatch); @@ -29,7 +29,7 @@ splitAt.addAll(>>{ theta, $.xl, $.cw, - { ...$.attribs } + copyAttribs($) ), new Arc( set([], $.pos), @@ -39,7 +39,7 @@ splitAt.addAll(>>{ $.end, $.xl, $.cw, - { ...$.attribs } + copyAttribs($) ) ]; }, @@ -54,12 +54,12 @@ splitAt.addAll(>>{ (pts) => new Line(pts, { ...attribs }) ), - [Type.POLYLINE]: ($: Polyline, t) => { - const res = new Sampler($.points).splitAt(t); - return res - ? res.map((pts) => new Polyline(copyVectors(pts), { ...$.attribs })) - : undefined; - }, + [Type.POLYLINE]: ($: Polyline, t) => + pointArraysAsShapes( + Polyline, + new Sampler($.points).splitAt(t), + $.attribs + ), [Type.QUADRATIC]: ({ attribs, points }: Quadratic, t: number) => quadraticSplitAt(points[0], points[1], points[2], t).map( diff --git a/packages/geom/src/ops/split-near.ts b/packages/geom/src/ops/split-near.ts index b6a903d1e0..3693593d66 100644 --- a/packages/geom/src/ops/split-near.ts +++ b/packages/geom/src/ops/split-near.ts @@ -5,14 +5,14 @@ import { closestT } from "@thi.ng/geom-closest-point"; import { Sampler } from "@thi.ng/geom-resample"; import { quadraticSplitNearPoint, splitCubicNearPoint } from "@thi.ng/geom-splines"; import { clamp01 } from "@thi.ng/math"; -import { copyVectors, ReadonlyVec } from "@thi.ng/vectors"; -import { - Cubic, - Line, - Polyline, - Quadratic -} from "../api"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { Cubic } from "../api/cubic"; +import { Line } from "../api/line"; +import { Polyline } from "../api/polyline"; +import { Quadratic } from "../api/quadratic"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; +import { pointArraysAsShapes } from "../internal/points-as-shape"; import { splitLine } from "../internal/split"; /** @@ -49,16 +49,16 @@ splitNearPoint.addAll(< [Type.LINE]: ($: Line, p) => { const t = closestT(p, $.points[0], $.points[1]) || 0; return splitLine($.points[0], $.points[1], clamp01(t)).map( - (pts) => new Line(pts, { ...$.attribs }) + (pts) => new Line(pts, copyAttribs($)) ); }, - [Type.POLYLINE]: ($: Polyline, p) => { - const res = new Sampler($.points).splitNear(p); - return res - ? res.map((pts) => new Polyline(copyVectors(pts), { ...$.attribs })) - : undefined; - }, + [Type.POLYLINE]: ($: Polyline, p) => + pointArraysAsShapes( + Polyline, + new Sampler($.points).splitNear(p), + $.attribs + ), [Type.QUADRATIC]: ({ points, attribs }: Quadratic, p) => quadraticSplitNearPoint(p, points[0], points[1], points[2]).map( diff --git a/packages/geom/src/ops/subdiv-curve.ts b/packages/geom/src/ops/subdiv-curve.ts index 1c860e6013..c1a78fd6c2 100644 --- a/packages/geom/src/ops/subdiv-curve.ts +++ b/packages/geom/src/ops/subdiv-curve.ts @@ -2,7 +2,9 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation2O, MultiFn2O } from "@thi.ng/defmulti"; import { IShape, SubdivKernel, Type } from "@thi.ng/geom-api"; import { subdivide } from "@thi.ng/geom-subdiv-curve"; -import { Polygon, Polyline } from "../api"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; export const subdivCurve: MultiFn2O< @@ -15,9 +17,9 @@ export const subdivCurve: MultiFn2O< subdivCurve.addAll(< IObjectOf> >{ - [Type.POLYGON]: (poly: Polygon, kernel, iter = 1) => - new Polygon(subdivide(poly.points, kernel, iter), { ...poly.attribs }), + [Type.POLYGON]: ($: Polygon, kernel, iter = 1) => + new Polygon(subdivide($.points, kernel, iter), copyAttribs($)), - [Type.POLYLINE]: (line: Polyline, kernel, iter = 1) => - new Polyline(subdivide(line.points, kernel, iter), { ...line.attribs }) + [Type.POLYLINE]: ($: Polyline, kernel, iter = 1) => + new Polyline(subdivide($.points, kernel, iter), copyAttribs($)) }); diff --git a/packages/geom/src/ops/tangent-at.ts b/packages/geom/src/ops/tangent-at.ts index 0e0b87a8cf..91c43d041c 100644 --- a/packages/geom/src/ops/tangent-at.ts +++ b/packages/geom/src/ops/tangent-at.ts @@ -4,7 +4,8 @@ import { IShape, PCLike, Type } from "@thi.ng/geom-api"; import { Sampler } from "@thi.ng/geom-resample"; import { cossin, HALF_PI, TAU } from "@thi.ng/math"; import { direction, Vec } from "@thi.ng/vectors"; -import { Line, Rect } from "../api"; +import { Line } from "../api/line"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; import { vertices } from "./vertices"; diff --git a/packages/geom/src/ops/transform.ts b/packages/geom/src/ops/transform.ts index 894cc632c0..81228326d4 100644 --- a/packages/geom/src/ops/transform.ts +++ b/packages/geom/src/ops/transform.ts @@ -4,34 +4,28 @@ import { IHiccupShape, IShape, PathSegment, - PCLike, - PCLikeConstructor, SegmentType, Type } from "@thi.ng/geom-api"; import { mulV, ReadonlyMat } from "@thi.ng/matrices"; import { map } from "@thi.ng/transducers"; -import { - Cubic, - Group, - Line, - Path, - Points, - Polygon, - Polyline, - Quad, - Quadratic, - Rect, - Triangle -} from "../api"; +import { Cubic } from "../api/cubic"; +import { Group } from "../api/group"; +import { Line } from "../api/line"; +import { Path } from "../api/path"; +import { Points } from "../api/points"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { Quad } from "../api/quad"; +import { Quadratic } from "../api/quadratic"; +import { Rect } from "../api/rect"; +import { Triangle } from "../api/triangle"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; -import { transformedPoints } from "../internal/transform-points"; +import { transformedShape as tx } from "../internal/transform-points"; import { asPath } from "./as-path"; import { asPolygon } from "./as-polygon"; -const tx = (ctor: PCLikeConstructor) => ($: PCLike, mat: ReadonlyMat) => - new ctor(transformedPoints($.points, mat), { ...$.attribs }); - /** * Transforms given shape with provided matrix. Some shape types will be * automatically converted to other types prior to transformation because they @@ -52,7 +46,7 @@ transform.addAll(>>{ [Type.GROUP]: ($: Group, mat) => new Group( - { ...$.attribs }, + copyAttribs($), $.children.map((x) => transform(x, mat)) ), @@ -75,7 +69,7 @@ transform.addAll(>>{ $.segments ) ], - $.attribs + copyAttribs($) ), [Type.POINTS]: tx(Points), diff --git a/packages/geom/src/ops/translate.ts b/packages/geom/src/ops/translate.ts index bdbafbc60e..97cf8854d0 100644 --- a/packages/geom/src/ops/translate.ts +++ b/packages/geom/src/ops/translate.ts @@ -1,12 +1,6 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation2 } from "@thi.ng/defmulti"; -import { - IHiccupShape, - IShape, - PCLike, - PCLikeConstructor, - Type -} from "@thi.ng/geom-api"; +import { IHiccupShape, IShape, Type } from "@thi.ng/geom-api"; import { add2, add3, @@ -14,34 +8,30 @@ import { set2, set3 } from "@thi.ng/vectors"; -import { - AABB, - Arc, - Circle, - Ellipse, - Group, - Line, - Path, - Points, - Polygon, - Polyline, - Quad, - Ray, - Rect, - Sphere, - Triangle -} from "../api"; +import { AABB } from "../api/aabb"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Ellipse } from "../api/ellipse"; +import { Group } from "../api/group"; +import { Line } from "../api/line"; +import { Path } from "../api/path"; +import { Points } from "../api/points"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { Quad } from "../api/quad"; +import { Ray } from "../api/ray"; +import { Rect } from "../api/rect"; +import { Sphere } from "../api/sphere"; +import { Triangle } from "../api/triangle"; +import { copyAttribs } from "../internal/copy-attribs"; import { dispatch } from "../internal/dispatch"; -import { translatedPoints } from "../internal/translate-points"; - -const tx = (ctor: PCLikeConstructor) => ($: PCLike, mat: ReadonlyVec) => - new ctor(translatedPoints($.points, mat), { ...$.attribs }); +import { translatedShape as tx } from "../internal/translate-points"; export const translate = defmulti(dispatch); translate.addAll(>>{ [Type.AABB]: ($: AABB, delta) => - new AABB(add3([], $.pos, delta), set3([], $.size), { ...$.attribs }), + new AABB(add3([], $.pos, delta), set3([], $.size), copyAttribs($)), [Type.ARC]: ($: Arc, delta) => { const a = $.copy(); @@ -50,14 +40,14 @@ translate.addAll(>>{ }, [Type.CIRCLE]: ($: Circle, delta) => - new Circle(add2([], $.pos, delta), $.r, { ...$.attribs }), + new Circle(add2([], $.pos, delta), $.r, copyAttribs($)), [Type.ELLIPSE]: ($: Ellipse, delta) => - new Ellipse(add2([], $.pos, delta), set2([], $.r), { ...$.attribs }), + new Ellipse(add2([], $.pos, delta), set2([], $.r), copyAttribs($)), [Type.GROUP]: ($: Group, delta) => new Group( - { ...$.attribs }, + copyAttribs($), $.children.map((s) => translate(s, delta)) ), @@ -76,7 +66,7 @@ translate.addAll(>>{ point: add2([], s.point!, delta) } ), - { ...$.attribs } + copyAttribs($) ), [Type.POINTS]: tx(Points), @@ -88,13 +78,13 @@ translate.addAll(>>{ [Type.QUAD]: tx(Quad), [Type.RAY]: ($: Ray, delta) => - new Ray(add2([], $.pos, delta), $.dir, { ...$.attribs }), + new Ray(add2([], $.pos, delta), $.dir, copyAttribs($)), [Type.RECT]: ($: Rect, delta) => - new Rect(add2([], $.pos, delta), set2([], $.size), { ...$.attribs }), + new Rect(add2([], $.pos, delta), set2([], $.size), copyAttribs($)), [Type.SPHERE]: ($: Sphere, delta) => - new Sphere(add3([], $.pos, delta), $.r, { ...$.attribs }), + new Sphere(add3([], $.pos, delta), $.r, copyAttribs($)), [Type.TRIANGLE]: tx(Triangle) }); diff --git a/packages/geom/src/ops/union.ts b/packages/geom/src/ops/union.ts index ef4888efa4..95ae9d033e 100644 --- a/packages/geom/src/ops/union.ts +++ b/packages/geom/src/ops/union.ts @@ -1,7 +1,8 @@ import { IObjectOf } from "@thi.ng/api"; import { defmulti, Implementation2 } from "@thi.ng/defmulti"; import { IShape, Type } from "@thi.ng/geom-api"; -import { AABB, Rect } from "../api"; +import { AABB } from "../api/aabb"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; import { unionBounds } from "../internal/union-bounds"; diff --git a/packages/geom/src/ops/unmap-point.ts b/packages/geom/src/ops/unmap-point.ts index 2024e9501f..cd5202b060 100644 --- a/packages/geom/src/ops/unmap-point.ts +++ b/packages/geom/src/ops/unmap-point.ts @@ -7,7 +7,8 @@ import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -import { Quad, Rect } from "../api"; +import { Quad } from "../api/quad"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; /** diff --git a/packages/geom/src/ops/vertices.ts b/packages/geom/src/ops/vertices.ts index 07d578ee27..a363cd8e2f 100644 --- a/packages/geom/src/ops/vertices.ts +++ b/packages/geom/src/ops/vertices.ts @@ -19,20 +19,18 @@ import { set2, Vec } from "@thi.ng/vectors"; -import { - AABB, - Arc, - Circle, - Cubic, - Ellipse, - Group, - Path, - Points, - Polygon, - Polyline, - Quadratic, - Rect -} from "../api"; +import { AABB } from "../api/aabb"; +import { Arc } from "../api/arc"; +import { Circle } from "../api/circle"; +import { Cubic } from "../api/cubic"; +import { Ellipse } from "../api/ellipse"; +import { Group } from "../api/group"; +import { Path } from "../api/path"; +import { Points } from "../api/points"; +import { Polygon } from "../api/polygon"; +import { Polyline } from "../api/polyline"; +import { Quadratic } from "../api/quadratic"; +import { Rect } from "../api/rect"; import { dispatch } from "../internal/dispatch"; export const vertices: MultiFn1O< diff --git a/packages/geom/src/ops/volume.ts b/packages/geom/src/ops/volume.ts index 84eb3a64a5..8e01ff5826 100644 --- a/packages/geom/src/ops/volume.ts +++ b/packages/geom/src/ops/volume.ts @@ -2,7 +2,8 @@ import { IObjectOf } from "@thi.ng/api"; import { DEFAULT, defmulti, Implementation1 } from "@thi.ng/defmulti"; import { IShape, Type } from "@thi.ng/geom-api"; import { PI } from "@thi.ng/math"; -import { AABB, Sphere } from "../api"; +import { AABB } from "../api/aabb"; +import { Sphere } from "../api/sphere"; import { dispatch } from "../internal/dispatch"; /** diff --git a/packages/grid-iterators/.npmignore b/packages/grid-iterators/.npmignore new file mode 100644 index 0000000000..89aa40d2db --- /dev/null +++ b/packages/grid-iterators/.npmignore @@ -0,0 +1,19 @@ +.cache +.meta +.nyc_output +*.gz +*.html +*.svg +*.tgz +*.h +*.o +*.wasm +build +coverage +dev +doc +export +src* +test +tools +tsconfig.json diff --git a/packages/grid-iterators/CHANGELOG.md b/packages/grid-iterators/CHANGELOG.md new file mode 100644 index 0000000000..8b49728159 --- /dev/null +++ b/packages/grid-iterators/CHANGELOG.md @@ -0,0 +1,25 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/grid-iterators@0.1.0...@thi.ng/grid-iterators@0.2.0) (2019-11-09) + + +### Features + +* **grid-iterators:** add interleave fns ([c883ea0](https://github.com/thi-ng/umbrella/commit/c883ea03d9a37698533d981a96f7122828731364)) +* **grid-iterators:** add z-curve & random iterators, add deps ([ba8ed18](https://github.com/thi-ng/umbrella/commit/ba8ed18cd84db77ccb35ed95586c66151cf1d690)) +* **grid-iterators:** add zigzagDiagonal(), update readme, rename files ([5630055](https://github.com/thi-ng/umbrella/commit/56300557f395698f82b453c79956ada72726444a)) +* **grid-iterators:** make row args optional ([60dccfc](https://github.com/thi-ng/umbrella/commit/60dccfcb0ba1d731eeecd4c12433d44b5491e7a7)) + + + + + +# 0.1.0 (2019-09-21) + + +### Features + +* **grid-iterators:** import as new package, incl. assets ([fe4ee00](https://github.com/thi-ng/umbrella/commit/fe4ee00)) diff --git a/packages/grid-iterators/LICENSE b/packages/grid-iterators/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/grid-iterators/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/grid-iterators/README.md b/packages/grid-iterators/README.md new file mode 100644 index 0000000000..a75bd4d7f6 --- /dev/null +++ b/packages/grid-iterators/README.md @@ -0,0 +1,145 @@ +# @thi.ng/grid-iterators + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/grid-iterators.svg)](https://www.npmjs.com/package/@thi.ng/grid-iterators) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/grid-iterators.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + +- [About](#about) + - [Diagonal](#diagonal) + - [Hilbert curve](#hilbert-curve) + - [Interleave columns](#interleave-columns) + - [Interleave rows](#interleave-rows) + - [Random](#random) + - [Outward spiral](#outward-spiral) + - [Z-curve](#z-curve) + - [Zigzag columns](#zigzag-columns) + - [Zigzag diagonal](#zigzag-diagonal) + - [Zigzag rows](#zigzag-rows) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) +- [Authors](#authors) +- [License](#license) + + + +## About + +Collection of 2D grid iterators, providing the 10 following orderings: + +### Diagonal + +![anim](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/grid-iterators/diagonal2d-small.gif) + +[Source](https://github.com/thi-ng/umbrella/tree/master/packages/grid-iterators/src/diagonal.ts) + +### Hilbert curve + +![anim](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/grid-iterators/hilbert2d-small.gif) + +[Source](https://github.com/thi-ng/umbrella/tree/master/packages/grid-iterators/src/hilbert.ts) + +### Interleave columns + +![anim](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/grid-iterators/interleavecolumns2d-small.gif) + +[Source](https://github.com/thi-ng/umbrella/tree/master/packages/grid-iterators/src/interleave.ts) + +Supports custom strides... example uses `step = 4` + +### Interleave rows + +![anim](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/grid-iterators/interleaverows2d-small.gif) + +[Source](https://github.com/thi-ng/umbrella/tree/master/packages/grid-iterators/src/interleave.ts) + +Supports custom strides... example uses `step = 4` + +### Random + +![anim](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/grid-iterators/random2d-small.gif) + +[Source](https://github.com/thi-ng/umbrella/tree/master/packages/grid-iterators/src/random.ts) + +Supports custom PRNG implementations via `IRandom` interface defined in +[@thi.ng/random](https://github.com/thi-ng/umbrella/tree/master/packages/random) + +### Outward spiral + +![anim](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/grid-iterators/spiral2d-small.gif) + +[Source](https://github.com/thi-ng/umbrella/tree/master/packages/grid-iterators/src/spiral.ts) + +### Z-curve + +![anim](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/grid-iterators/zcurve2d-small.gif) + +[Source](https://github.com/thi-ng/umbrella/tree/master/packages/grid-iterators/src/zcurve.ts) + +### Zigzag columns + +![anim](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/grid-iterators/zigzagcolumns2d-small.gif) + +[Source](https://github.com/thi-ng/umbrella/tree/master/packages/grid-iterators/src/zigzag-columns.ts) + +### Zigzag diagonal + +![anim](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/grid-iterators/zigzagdiag2d-small.gif) + +[Source](https://github.com/thi-ng/umbrella/tree/master/packages/grid-iterators/src/zigzag-diagonal.ts) + +### Zigzag rows + +![anim](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/grid-iterators/zigzagrows2d-small.gif) + +[Source](https://github.com/thi-ng/umbrella/tree/master/packages/grid-iterators/src/zigzag-rows.ts) + +Some functions have been ported from [Christopher +Kulla](https://fpsunflower.github.io/ckulla/)'s Java-based [Sunflow +renderer](https://sunflow.sf.net). + +For more basic 2D/3D grid iteration, also see `range2d()` & `range3d()` +in +[@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers). + +## Installation + +```bash +yarn add @thi.ng/grid-iterators +``` + +## Dependencies + +- [@thi.ng/arrays](https://github.com/thi-ng/umbrella/tree/master/packages/arrays) +- [@thi.ng/binary](https://github.com/thi-ng/umbrella/tree/master/packages/binary) +- [@thi.ng/morton](https://github.com/thi-ng/umbrella/tree/master/packages/morton) +- [@thi.ng/random](https://github.com/thi-ng/umbrella/tree/master/packages/random) +- [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) + +## Usage examples + +```ts +import * as gi from "@thi.ng/grid-iterators"; + +[...gi.zigzagRows2d(4, 4)] + +// [ +// [ 0, 0 ], [ 1, 0 ], [ 2, 0 ], [ 3, 0 ], +// [ 3, 1 ], [ 2, 1 ], [ 1, 1 ], [ 0, 1 ], +// [ 0, 2 ], [ 1, 2 ], [ 2, 2 ], [ 3, 2 ], +// [ 3, 3 ], [ 2, 3 ], [ 1, 3 ], [ 0, 3 ] +// ] +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2019 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/grid-iterators/package.json b/packages/grid-iterators/package.json new file mode 100644 index 0000000000..50b68d45cf --- /dev/null +++ b/packages/grid-iterators/package.json @@ -0,0 +1,61 @@ +{ + "name": "@thi.ng/grid-iterators", + "version": "0.2.0", + "description": "2D grid iterators w/ multiple orderings", + "module": "./index.js", + "main": "./lib/index.js", + "umd:main": "./lib/index.umd.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/grid-iterators", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module", + "build:release": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module all", + "build:es6": "tsc --declaration", + "build:test": "rimraf build && tsc -p test/tsconfig.json", + "build:assets": "node tools/build-assets", + "test": "yarn build:test && mocha build/test/*.js", + "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib", + "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", + "pub": "yarn build:release && yarn publish --access public" + }, + "devDependencies": { + "@types/mocha": "^5.2.6", + "@types/node": "^12.6.3", + "mocha": "^6.1.4", + "nyc": "^14.0.0", + "typedoc": "^0.15.0", + "typescript": "^3.6.4" + }, + "dependencies": { + "@thi.ng/arrays": "^0.3.0", + "@thi.ng/binary": "^1.1.1", + "@thi.ng/morton": "^1.1.4", + "@thi.ng/random": "^1.1.13", + "@thi.ng/transducers": "^6.0.0" + }, + "keywords": [ + "ES6", + "2D", + "diagonal", + "grid", + "hilbert", + "iterator", + "morton", + "random", + "spiral", + "typescript", + "Z-curve", + "zigzag" + ], + "publishConfig": { + "access": "public" + }, + "sideEffects": false +} diff --git a/packages/grid-iterators/src/diagonal.ts b/packages/grid-iterators/src/diagonal.ts new file mode 100644 index 0000000000..dc6ceb08cc --- /dev/null +++ b/packages/grid-iterators/src/diagonal.ts @@ -0,0 +1,30 @@ +/** + * Yields sequence of 2D grid coordinates in diagonal order starting at + * [0,0] and using given `cols` and `rows`. Each diagonal starts at y=0 + * and progresses in -x,+y direction. + * + * Ported & modified from original Java code by Christopher Kulla. + * https://sourceforge.net/p/sunflow/code/HEAD/tree/trunk/src/org/sunflow/core/bucket/DiagonalBucketOrder.java + * + * @param cols + * @param rows + */ +export function* diagonal2d(cols: number, rows = cols) { + const num = cols * rows - 1; + for (let x = 0, y = 0, nx = 1, ny = 0, i = 0; i <= num; i++) { + yield [x, y]; + if (i != num) { + do { + if (y === ny) { + y = 0; + x = nx; + ny++; + nx++; + } else { + x--; + y++; + } + } while (y >= rows || x >= cols); + } + } +} diff --git a/packages/grid-iterators/src/hilbert.ts b/packages/grid-iterators/src/hilbert.ts new file mode 100644 index 0000000000..daae07b2f4 --- /dev/null +++ b/packages/grid-iterators/src/hilbert.ts @@ -0,0 +1,64 @@ +/** + * Yields sequence of 2D grid coordinates along 2D Hilbert curve using + * given `cols` and `rows` (each max. 32768 (2^15)). + * + * Ported & modified from original Java code by Christopher Kulla. + * https://sourceforge.net/p/sunflow/code/HEAD/tree/trunk/src/org/sunflow/core/bucket/HilbertBucketOrder.java + * + * @param cols + * @param rows + */ +export function* hilbert2d(cols: number, rows = cols) { + let hIndex = 0; // hilbert curve index + let hOrder = 0; // hilbert curve order + // fit to number of buckets + while ((1 << hOrder < cols || 1 << hOrder < rows) && hOrder < 15) hOrder++; + const numBuckets = 1 << (2 * hOrder); + for (let i = 0, n = cols * rows; i < n; i++) { + let hx, hy; + do { + // adapted from Hacker's Delight + let s, t, comp, swap, cs, sr; + // s is the hilbert index, shifted to start in the middle + s = hIndex | (0x55555555 << (2 * hOrder)); // Pad s on left with 01 + sr = (s >>> 1) & 0x55555555; // (no change) groups. + cs = ((s & 0x55555555) + sr) ^ 0x55555555; + // Compute complement & swap info in two-bit groups. + // Parallel prefix xor op to propagate both complement and + // swap info together from left to right (there is no step + // "cs ^= cs >> 1", so in effect it computes two independent + // parallel prefix operations on two interleaved sets of + // sixteen bits). + cs = cs ^ (cs >>> 2); + cs = cs ^ (cs >>> 4); + cs = cs ^ (cs >>> 8); + cs = cs ^ (cs >>> 16); + // Separate the swap and complement bits. + swap = cs & 0x55555555; + comp = (cs >>> 1) & 0x55555555; + // Calculate x and y in the odd & even bit positions + t = (s & swap) ^ comp; + s = s ^ sr ^ t ^ (t << 1); + // Clear out any junk on the left (unpad). + s = s & ((1 << (2 * hOrder)) - 1); + // Now "unshuffle" to separate the x and y bits. + t = (s ^ (s >>> 1)) & 0x22222222; + s = s ^ t ^ (t << 1); + t = (s ^ (s >>> 2)) & 0x0c0c0c0c; + s = s ^ t ^ (t << 2); + t = (s ^ (s >>> 4)) & 0x00f000f0; + s = s ^ t ^ (t << 4); + t = (s ^ (s >>> 8)) & 0x0000ff00; + s = s ^ t ^ (t << 8); + // Assign the two halves to x and y. + hx = s >>> 16; + hy = s & 0xffff; + hIndex++; + } while ( + // Dont't emit any outside cells + (hx >= cols || hy >= rows || hx < 0 || hy < 0) && + hIndex < numBuckets + ); + yield [hx, hy]; + } +} diff --git a/packages/grid-iterators/src/index.ts b/packages/grid-iterators/src/index.ts new file mode 100644 index 0000000000..0cfbe90f06 --- /dev/null +++ b/packages/grid-iterators/src/index.ts @@ -0,0 +1,9 @@ +export * from "./diagonal"; +export * from "./hilbert"; +export * from "./interleave"; +export * from "./random"; +export * from "./spiral"; +export * from "./zcurve"; +export * from "./zigzag-columns"; +export * from "./zigzag-diagonal"; +export * from "./zigzag-rows"; diff --git a/packages/grid-iterators/src/interleave.ts b/packages/grid-iterators/src/interleave.ts new file mode 100644 index 0000000000..0b08501a6f --- /dev/null +++ b/packages/grid-iterators/src/interleave.ts @@ -0,0 +1,43 @@ +import { map, range2d } from "@thi.ng/transducers"; + +/** + * Yields 2D grid coordinates in the order of interleaved columns with + * configurable `step` size (default: 2). I.e. returns columns in this + * order: + * + * - 0, step, 2 * step, 3 * step... + * - 1, 2 * step + 1, 3 * step + 1... + * - etc. + * + * @see interleaveRows2d + * + * @param cols + * @param rows + * @param step + */ +export function* interleaveColumns2d(cols: number, rows = cols, step = 2) { + for (let j = 0; j < step; j++) { + yield* map((p) => [p[1], p[0]], range2d(0, rows, j, cols, 1, step)); + } +} + +/** + * Similar to `interleaveColumns2d`, but yields 2D grid coordinates in + * the order of interleaved rows with configurable `step` size (default: + * 2). I.e. returns rows in this order: + * + * - 0, step, 2 * step, 3 * step... + * - 1, 2 * step + 1, 3 * step + 1... + * - etc. + * + * @see interleaveColumns2d + * + * @param cols + * @param rows + * @param step + */ +export function* interleaveRows2d(cols: number, rows = cols, step = 2) { + for (let j = 0; j < step; j++) { + yield* range2d(0, cols, j, rows, 1, step); + } +} diff --git a/packages/grid-iterators/src/random.ts b/packages/grid-iterators/src/random.ts new file mode 100644 index 0000000000..35667850b7 --- /dev/null +++ b/packages/grid-iterators/src/random.ts @@ -0,0 +1,19 @@ +import { shuffle } from "@thi.ng/arrays"; +import { IRandom, SYSTEM } from "@thi.ng/random"; +import { range } from "@thi.ng/transducers"; + +/** + * Yields 2D grid coordinates in random order w/ support for optional + * `IRandom` implementation (default: `SYSTEM` aka `Math.random`). + * + * @see thi.ng/random + * + * @param cols + * @param rows + * @param rnd + */ +export function* random2d(cols: number, rows = cols, rnd: IRandom = SYSTEM) { + for (let i of shuffle([...range(cols * rows)], undefined, rnd)) { + yield [i % cols, (i / cols) | 0]; + } +} diff --git a/packages/grid-iterators/src/spiral.ts b/packages/grid-iterators/src/spiral.ts new file mode 100644 index 0000000000..f9001d95d5 --- /dev/null +++ b/packages/grid-iterators/src/spiral.ts @@ -0,0 +1,44 @@ +/** + * Yields sequence of 2D grid coordinates in outward spiral order + * starting from the center, given `cols` and `rows`. + * + * Ported & modified from original Java code by Christopher Kulla. + * https://sourceforge.net/p/sunflow/code/HEAD/tree/trunk/src/org/sunflow/core/bucket/SpiralBucketOrder.java + * + * @param cols + * @param rows + */ +export function* spiral2d(cols: number, rows = cols) { + const num = cols * rows; + const center = (Math.min(cols, rows) - 1) >> 1; + for (let i = 0; i < num; i++) { + let nx = cols; + let ny = rows; + while (i < nx * ny) { + nx--; + ny--; + } + const nxny = nx * ny; + const minnxny = Math.min(nx, ny); + const m2 = minnxny >> 1; + let bx, by; + if (minnxny & 1) { + if (i <= nxny + ny) { + bx = nx - m2; + by = -m2 + i - nxny; + } else { + bx = nx - m2 - (i - (nxny + ny)); + by = ny - m2; + } + } else { + if (i <= nxny + ny) { + bx = -m2; + by = ny - m2 - (i - nxny); + } else { + bx = -m2 + (i - (nxny + ny)); + by = -m2; + } + } + yield [bx + center, by + center]; + } +} diff --git a/packages/grid-iterators/src/zcurve.ts b/packages/grid-iterators/src/zcurve.ts new file mode 100644 index 0000000000..ffb2d7fa03 --- /dev/null +++ b/packages/grid-iterators/src/zcurve.ts @@ -0,0 +1,21 @@ +import { ceilPow2 } from "@thi.ng/binary"; +import { demux2 } from "@thi.ng/morton"; + +/** + * Yields 2D grid coordinates in Z-curve (Morton) order. A perfect + * Z-curve is only generated if `cols` AND `rows` are equal and a power + * of 2. Due to using 32bit morton codes, only supports grid sizes up to + * 32767 (0x7fff) in either dimension. + * + * @param cols + * @param rows + */ +export function* zcurve2d(cols: number, rows = cols) { + const max = ceilPow2(Math.pow(Math.max(cols, rows), 2)); + for (let i = 0; i < max; i++) { + const p = demux2(i); + if (p[0] < cols && p[1] < rows) { + yield p; + } + } +} diff --git a/packages/grid-iterators/src/zigzag-columns.ts b/packages/grid-iterators/src/zigzag-columns.ts new file mode 100644 index 0000000000..fca0f254df --- /dev/null +++ b/packages/grid-iterators/src/zigzag-columns.ts @@ -0,0 +1,20 @@ +/** + * Yields sequence of 2D grid coordinates in zigzag column order + * starting from [0,0], given `cols` and `rows`. + * + * Ported & modified from original Java code by Christopher Kulla. + * https://sourceforge.net/p/sunflow/code/HEAD/tree/trunk/src/org/sunflow/core/bucket/SpiralBucketOrder.java + * + * @param cols + * @param rows + * + */ +export function* zigzagColumns2d(cols: number, rows = cols) { + const num = cols * rows; + for (let i = 0; i < num; i++) { + const x = (i / rows) | 0; + let y = i % rows; + x & 1 && (y = rows - 1 - y); + yield [x, y]; + } +} diff --git a/packages/grid-iterators/src/zigzag-diagonal.ts b/packages/grid-iterators/src/zigzag-diagonal.ts new file mode 100644 index 0000000000..43968702ec --- /dev/null +++ b/packages/grid-iterators/src/zigzag-diagonal.ts @@ -0,0 +1,37 @@ +/** + * Similar to `diagonal2d`, but yields 2D grid coordinates in zigzag + * diagonal order starting at [0,0] and using given `cols` and `rows`. + * + * @param cols + * @param rows + */ +export function* zigzagDiagonal2d(cols: number, rows = cols) { + const num = cols * rows - 1; + for ( + let x = 0, y = 0, ny = 0, dx = -1, dy = 1, d = 0, down = true, i = 0; + i <= num; + i++ + ) { + yield [x, y]; + if (i !== num) { + do { + if (y === ny) { + if (down) { + y++; + d++; + ny = 0; + } else { + x++; + ny = ++d; + } + down = !down; + dx *= -1; + dy *= -1; + } else { + x += dx; + y += dy; + } + } while (x >= cols || y >= rows); + } + } +} diff --git a/packages/grid-iterators/src/zigzag-rows.ts b/packages/grid-iterators/src/zigzag-rows.ts new file mode 100644 index 0000000000..0e35d44d84 --- /dev/null +++ b/packages/grid-iterators/src/zigzag-rows.ts @@ -0,0 +1,20 @@ +/** + * Yields sequence of 2D grid coordinates in zigzag row order starting + * from [0,0], given `cols` and `rows`. + * + * Ported & modified from original Java code by Christopher Kulla. + * https://sourceforge.net/p/sunflow/code/HEAD/tree/trunk/src/org/sunflow/core/bucket/SpiralBucketOrder.java + * + * @param cols + * @param rows + * + */ +export function* zigzagRows2d(cols: number, rows = cols) { + const num = cols * rows; + for (let i = 0; i < num; i++) { + let x = i % cols; + const y = (i / cols) | 0; + y & 1 && (x = cols - 1 - x); + yield [x, y]; + } +} diff --git a/packages/grid-iterators/test/index.ts b/packages/grid-iterators/test/index.ts new file mode 100644 index 0000000000..ae394d6d00 --- /dev/null +++ b/packages/grid-iterators/test/index.ts @@ -0,0 +1,6 @@ +// import * as assert from "assert"; +// import * as gi from "../src/index"; + +describe("grid-iterators", () => { + it("tests pending"); +}); diff --git a/packages/grid-iterators/test/tsconfig.json b/packages/grid-iterators/test/tsconfig.json new file mode 100644 index 0000000000..f6e63560dd --- /dev/null +++ b/packages/grid-iterators/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build", + "module": "commonjs" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/grid-iterators/tools/build-assets.js b/packages/grid-iterators/tools/build-assets.js new file mode 100644 index 0000000000..6a3c992f57 --- /dev/null +++ b/packages/grid-iterators/tools/build-assets.js @@ -0,0 +1,66 @@ +const g = require("@thi.ng/geom"); +const gi = require("@thi.ng/grid-iterators"); +const str = require("@thi.ng/strings"); +const fs = require("fs"); +const execSync = require("child_process").execSync; + +try { + fs.mkdirSync("export"); +} catch (e) {} + +[ + gi.diagonal2d, + gi.hilbert2d, + gi.interleaveColumns2d, + gi.interleaveRows2d, + gi.random2d, + gi.spiral2d, + gi.zcurve2d, + gi.zigzagColumns2d, + gi.zigzagDiagonal2d, + gi.zigzagRows2d +].forEach((fn) => { + console.log(`generating ${fn.name}...`); + const pts = [...fn(16)]; + const base = `export/${fn.name.toLowerCase()}`; + for (let i = 1; i <= 128; i++) { + fs.writeFileSync( + `${base}-${str.Z3(i)}.svg`, + g.asSvg( + g.svgDoc( + { + width: 600, + height: 600, + viewBox: "-1 -1 18 18", + stroke: "black", + "stroke-width": 0.1 + }, + g.polyline( + pts.slice(0, i * 2).map(([x, y]) => [x + 0.5, y + 0.5]) + ), + g.group( + { fill: [0, 1, 0.5, 0.25], stroke: "none" }, + pts.slice(0, i * 2 - 1).map((p) => g.rect(p, 1)) + ), + g.rect(pts[i * 2 - 1], 1, { + fill: [0, 1, 0.5, 0.85], + stroke: "none" + }) + ) + ) + ); + } + console.log(`\tconverting to PNG...`); + execSync(`../../scripts/svg2png ${base}*.svg`); + console.log(`\tremoving SVG files...`); + execSync(`rm ${base}*.svg`); + console.log(`\tbuilding GIFs...`); + execSync(`gm convert -delay 6 ${base}*.png ${base}.gif`); + execSync( + `gm convert -delay 6 -resize 200x200 ${base}*.png ${base}-small.gif` + ); + console.log(`\tbuilding MP4...`); + execSync( + `ffmpeg -r 30 -i ${base}-%03d.png -c:v libx264 -pix_fmt yuv420p -y ${base}.mp4` + ); +}); diff --git a/packages/grid-iterators/tsconfig.json b/packages/grid-iterators/tsconfig.json new file mode 100644 index 0000000000..893b9979c5 --- /dev/null +++ b/packages/grid-iterators/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "module": "es6", + "target": "es6" + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/hdom-canvas/CHANGELOG.md b/packages/hdom-canvas/CHANGELOG.md index dcd5d2c7d0..2adcd4e902 100644 --- a/packages/hdom-canvas/CHANGELOG.md +++ b/packages/hdom-canvas/CHANGELOG.md @@ -3,6 +3,71 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.4.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.3.1...@thi.ng/hdom-canvas@2.4.0) (2019-11-09) + + +### Features + +* **hdom-canvas:** add `packedPoints` shape type, update readme ([292611a](https://github.com/thi-ng/umbrella/commit/292611a44d1a661dcad4c293863517cac3791f28)) + + + + + +## [2.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.3.0...@thi.ng/hdom-canvas@2.3.1) (2019-09-23) + +**Note:** Version bump only for package @thi.ng/hdom-canvas + + + + + +# [2.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.2.4...@thi.ng/hdom-canvas@2.3.0) (2019-09-21) + + +### Features + +* **hdom-canvas:** add clip attrib support for paths ([2c2909d](https://github.com/thi-ng/umbrella/commit/2c2909d)) + + + + + +## [2.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.2.3...@thi.ng/hdom-canvas@2.2.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hdom-canvas + + + + + +## [2.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.2.2...@thi.ng/hdom-canvas@2.2.3) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/hdom-canvas + + + + + +## [2.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.2.1...@thi.ng/hdom-canvas@2.2.2) (2019-08-16) + + +### Bug Fixes + +* **hdom-canvas:** fix attrib default vals, add missing weight val ([f09677f](https://github.com/thi-ng/umbrella/commit/f09677f)) + + + + + +## [2.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.2.0...@thi.ng/hdom-canvas@2.2.1) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/hdom-canvas + + + + + # [2.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-canvas@2.1.2...@thi.ng/hdom-canvas@2.2.0) (2019-07-31) diff --git a/packages/hdom-canvas/README.md b/packages/hdom-canvas/README.md index fbae43052c..84ae943287 100644 --- a/packages/hdom-canvas/README.md +++ b/packages/hdom-canvas/README.md @@ -33,6 +33,7 @@ This project is part of the - [Path](#path) - [SVG paths with arc segments](#svg-paths-with-arc-segments) - [Points](#points) + - [Packed points](#packed-points) - [Text](#text) - [Image](#image) - [Gradients](#gradients) @@ -41,7 +42,7 @@ This project is part of the - [String](#string) - [Number](#number) - [Array](#array) - - [[@thi.ng/color](https://github.com/thi-ng/umbrella/tree/master/packages/color) values](#thingcolorhttpsgithubcomthi-ngumbrellatreemasterpackagescolor-values) + - [@thi.ng/color values](#thingcolor-values) - [Coordinate transformations](#coordinate-transformations) - [Transform matrix](#transform-matrix) - [Override transform](#override-transform) @@ -399,6 +400,34 @@ The following shape specific attributes are used: - `shape`: `circle` or `rect` (default) - `size`: point size (radius for circles, width for rects) - default: 1 +### Packed points + +Similar to `points`, but uses a single packed buffer for all point +coordinates. + +```ts +["packedPoints", attribs, [x1,y1, x2,y2,...]] +``` + +Optional start index, number of points, component & point stride lengths +(number of indices between each vector component and each point +respectively) can be given as attributes. + +Defaults: + +- start index: 0 +- number of points: (array_length - start) / estride +- component stride: 1 +- element stride: 2 + +```ts +["packedPoints", { cstride: 1, estride: 4 }, + [x1, y1, 0, 0, x2, y2, 0, 0, ...]] + +["packedPoints", { offset: 8, num: 3, cstride: 4, estride: 1 }, + [0, 0, 0, 0, 0, 0, 0, 0, x1, x2, x3, 0, y1, y2, y3, 0...]] +``` + ### Text ```ts @@ -494,16 +523,19 @@ Interpreted as float RGB(A): `{ fill: [1, 0.8, 0.6, 0.4] }` => `{ fill: "rgba(255,204,153,0.40)" }` -#### [@thi.ng/color](https://github.com/thi-ng/umbrella/tree/master/packages/color) values +#### @thi.ng/color values -Converted to CSS color strings: +Colors defined via the +[@thi.ng/color](https://github.com/thi-ng/umbrella/tree/master/packages/color) +package can be automatically converted to CSS color strings: `{ fill: hcya(0.1666, 1, 0.8859) }` => `{ fill: "#ffff00" }` ### Coordinate transformations Coordinate system transformations can be achieved via the following -attributes. Nested transformations are supported. +attributes (for groups and individual shapes). +Nested transformations are supported. If using a combination of `translate`, `scale` and/or `rotate` attribs, the order of application is always TRS. diff --git a/packages/hdom-canvas/package.json b/packages/hdom-canvas/package.json index 31bc109ab9..037d0a0666 100644 --- a/packages/hdom-canvas/package.json +++ b/packages/hdom-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom-canvas", - "version": "2.2.0", + "version": "2.4.0", "description": "Declarative canvas scenegraph & visualization for @thi.ng/hdom", "module": "./index.js", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "build:test": "rimraf build && tsc -p test/tsconfig.json", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", - "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib draw", "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", "pub": "yarn build:release && yarn publish --access public" }, @@ -29,15 +29,17 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/color": "^1.0.0", - "@thi.ng/diff": "^3.2.2", - "@thi.ng/hdom": "^8.0.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/color": "^1.1.2", + "@thi.ng/diff": "^3.2.5", + "@thi.ng/hdom": "^8.0.7", + "@thi.ng/math": "^1.5.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "ES6", diff --git a/packages/hdom-canvas/src/api.ts b/packages/hdom-canvas/src/api.ts new file mode 100644 index 0000000000..387f268f31 --- /dev/null +++ b/packages/hdom-canvas/src/api.ts @@ -0,0 +1,8 @@ +import { IObjectOf } from "@thi.ng/api"; + +export interface DrawState { + attribs: IObjectOf; + grads?: IObjectOf; + edits: string[]; + restore?: boolean; +} diff --git a/packages/hdom-canvas/src/draw/arc.ts b/packages/hdom-canvas/src/draw/arc.ts new file mode 100644 index 0000000000..b9484d6833 --- /dev/null +++ b/packages/hdom-canvas/src/draw/arc.ts @@ -0,0 +1,33 @@ +import { IObjectOf } from "@thi.ng/api"; +import { TAU } from "@thi.ng/math"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { endShape } from "./end-shape"; + +export const circularArc = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pos: ReadonlyVec, + r: number, + start = 0, + end = TAU, + antiCCW = false +) => { + ctx.beginPath(); + ctx.arc(pos[0], pos[1], r, start, end, antiCCW); + endShape(ctx, attribs); +}; + +export const ellipticArc = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pos: ReadonlyVec, + r: ReadonlyVec, + axis = 0, + start = 0, + end = TAU, + ccw = false +) => { + ctx.beginPath(); + ctx.ellipse(pos[0], pos[1], r[0], r[1], axis, start, end, ccw); + endShape(ctx, attribs); +}; diff --git a/packages/hdom-canvas/src/draw/color.ts b/packages/hdom-canvas/src/draw/color.ts new file mode 100644 index 0000000000..e5b6916866 --- /dev/null +++ b/packages/hdom-canvas/src/draw/color.ts @@ -0,0 +1,36 @@ +import { isString } from "@thi.ng/checks"; +import { resolveAsCSS } from "@thi.ng/color"; +import { DrawState } from "../api"; + +export const resolveColor = (v: any) => (isString(v) ? v : resolveAsCSS(v)); + +export const resolveGradientOrColor = (state: DrawState, v: any) => + isString(v) + ? v[0] === "$" + ? state.grads![v.substr(1)] + : v + : resolveAsCSS(v); + +export const defLinearGradient = ( + ctx: CanvasRenderingContext2D, + { from, to }: any, + stops: any[][] +) => { + const g = ctx.createLinearGradient(from[0], from[1], to[0], to[1]); + for (let s of stops) { + g.addColorStop(s[0], resolveColor(s[1])); + } + return g; +}; + +export const defRadialGradient = ( + ctx: CanvasRenderingContext2D, + { from, to, r1, r2 }: any, + stops: any[][] +) => { + const g = ctx.createRadialGradient(from[0], from[1], r1, to[0], to[1], r2); + for (let s of stops) { + g.addColorStop(s[0], resolveColor(s[1])); + } + return g; +}; diff --git a/packages/hdom-canvas/src/draw/end-shape.ts b/packages/hdom-canvas/src/draw/end-shape.ts new file mode 100644 index 0000000000..14acf0d4d7 --- /dev/null +++ b/packages/hdom-canvas/src/draw/end-shape.ts @@ -0,0 +1,17 @@ +import { IObjectOf } from "@thi.ng/api"; + +export const endShape = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf +) => { + let v: any; + if ((v = attribs.fill) && v !== "none") { + ctx.fill(); + } + if ((v = attribs.stroke) && v !== "none") { + ctx.stroke(); + } + if ((v = attribs.clip)) { + ctx.clip(v === true ? "nonzero" : v); + } +}; diff --git a/packages/hdom-canvas/src/draw/image.ts b/packages/hdom-canvas/src/draw/image.ts new file mode 100644 index 0000000000..11f69f0136 --- /dev/null +++ b/packages/hdom-canvas/src/draw/image.ts @@ -0,0 +1,28 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; + +export const image = ( + ctx: CanvasRenderingContext2D, + _: IObjectOf, + { width, height }: IObjectOf, + img: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap, + dpos: ReadonlyVec, + spos?: ReadonlyVec, + ssize?: ReadonlyVec +) => { + width = width || img.width; + height = height || img.height; + spos + ? ctx.drawImage( + img, + spos[0], + spos[1], + ssize ? ssize[0] : width, + ssize ? ssize[1] : height, + dpos[0], + dpos[1], + width, + height + ) + : ctx.drawImage(img, dpos[0], dpos[1], width, height); +}; diff --git a/packages/hdom-canvas/src/draw/line.ts b/packages/hdom-canvas/src/draw/line.ts new file mode 100644 index 0000000000..43d641f30d --- /dev/null +++ b/packages/hdom-canvas/src/draw/line.ts @@ -0,0 +1,15 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; + +export const line = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + a: ReadonlyVec, + b: ReadonlyVec +) => { + if (attribs.stroke === "none") return; + ctx.beginPath(); + ctx.moveTo(a[0], a[1]); + ctx.lineTo(b[0], b[1]); + ctx.stroke(); +}; diff --git a/packages/hdom-canvas/src/draw/packed-points.ts b/packages/hdom-canvas/src/draw/packed-points.ts new file mode 100644 index 0000000000..e4b260ea24 --- /dev/null +++ b/packages/hdom-canvas/src/draw/packed-points.ts @@ -0,0 +1,48 @@ +import { IObjectOf } from "@thi.ng/api"; +import { TAU } from "@thi.ng/math"; + +export const packedPoints = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + opts: IObjectOf, + pts: ArrayLike +) => { + let v: any; + if ((v = attribs.fill) && v !== "none") { + __drawPoints(ctx, opts, pts, "fill", "fillRect"); + } + if ((v = attribs.stroke) && v !== "none") { + __drawPoints(ctx, opts, pts, "stroke", "strokeRect"); + } +}; + +const __drawPoints = ( + ctx: CanvasRenderingContext2D, + opts: IObjectOf, + pts: ArrayLike, + cmd: "fill" | "stroke", + cmdR: "fillRect" | "strokeRect" +) => { + const { start, cstride, estride, size } = { + start: 0, + cstride: 1, + estride: 2, + size: 1, + ...opts + }; + let num = + opts && opts.num != null + ? opts.num + : ((pts.length - start) / estride) | 0; + if (opts.shape === "circle") { + for (let i = start; --num >= 0; i += estride) { + ctx.beginPath(); + ctx.arc(pts[i], pts[i + cstride], size, 0, TAU); + ctx[cmd](); + } + } else { + for (let i = start; --num >= 0; i += estride) { + ctx[cmdR](pts[i], pts[i + cstride], size, size); + } + } +}; diff --git a/packages/hdom-canvas/src/draw/path.ts b/packages/hdom-canvas/src/draw/path.ts new file mode 100644 index 0000000000..1c6b44ec84 --- /dev/null +++ b/packages/hdom-canvas/src/draw/path.ts @@ -0,0 +1,113 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { endShape } from "./end-shape"; + +export const path = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + segments: any[] +) => { + ctx.beginPath(); + let a: ReadonlyVec = [0, 0]; + for (let i = 0, n = segments.length; i < n; i++) { + const s = segments[i]; + let b = s[1], + c, + d; + switch (s[0]) { + // move to + case "m": + b = [a[0] + b[0], a[1] + b[1]]; + case "M": + ctx.moveTo(b[0], b[1]); + a = b; + break; + // line to + case "l": + b = [a[0] + b[0], a[1] + b[1]]; + case "L": + ctx.lineTo(b[0], b[1]); + a = b; + break; + // horizontal line rel + case "h": + b = [a[0] + b, a[1]]; + ctx.lineTo(b[0], b[1]); + a = b; + break; + // horizontal line abs + case "H": + b = [b, a[1]]; + ctx.lineTo(b[0], b[1]); + a = b; + break; + // vertical line rel + case "v": + b = [a[0], a[1] + b]; + ctx.lineTo(b[0], b[1]); + a = b; + break; + // vertical line abs + case "V": + b = [a[0], b]; + ctx.lineTo(b[0], b[1]); + a = b; + break; + // cubic curve rel + case "c": + c = s[2]; + d = s[3]; + d = [a[0] + d[0], a[1] + d[1]]; + ctx.bezierCurveTo( + a[0] + b[0], + a[1] + b[1], + a[0] + c[0], + a[1] + c[1], + d[0], + d[1] + ); + a = d; + break; + // cubic curve abs + case "C": + c = s[2]; + d = s[3]; + ctx.bezierCurveTo(b[0], b[1], c[0], c[1], d[0], d[1]); + a = d; + break; + // quadratic curve rel + case "q": + c = s[2]; + c = [a[0] + c[0], a[1] + c[1]]; + ctx.quadraticCurveTo(a[0] + b[0], a[1] + b[1], c[0], c[1]); + a = c; + break; + // quadratic curve abs + case "Q": + c = s[2]; + ctx.quadraticCurveTo(b[0], b[1], c[0], c[1]); + a = c; + break; + // circular arc rel + // Note: NOT compatible w/ SVG arc segments + case "a": + c = s[2]; + c = [a[0] + c[0], a[1] + c[1]]; + ctx.arcTo(a[0] + b[0], a[1] + b[1], c[0], c[1], s[3]); + a = c; + break; + // circular arc abs + // Note: NOT compatible w/ SVG arc segments + case "A": + c = s[2]; + ctx.arcTo(b[0], b[1], c[0], c[1], s[3]); + a = c; + break; + // close path + case "z": + case "Z": + ctx.closePath(); + } + } + endShape(ctx, attribs); +}; diff --git a/packages/hdom-canvas/src/draw/points.ts b/packages/hdom-canvas/src/draw/points.ts new file mode 100644 index 0000000000..6b6fc1fa25 --- /dev/null +++ b/packages/hdom-canvas/src/draw/points.ts @@ -0,0 +1,39 @@ +import { IObjectOf } from "@thi.ng/api"; +import { TAU } from "@thi.ng/math"; +import { ReadonlyVec } from "@thi.ng/vectors"; + +export const points = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + opts: IObjectOf, + pts: Iterable +) => { + let v: any; + if ((v = attribs.fill) && v !== "none") { + __drawPoints(ctx, opts, pts, "fill", "fillRect"); + } + if ((v = attribs.stroke) && v !== "none") { + __drawPoints(ctx, opts, pts, "stroke", "strokeRect"); + } +}; + +const __drawPoints = ( + ctx: CanvasRenderingContext2D, + opts: IObjectOf, + pts: Iterable, + cmd: "fill" | "stroke", + cmdR: "fillRect" | "strokeRect" +) => { + const s: number = (opts && opts.size) || 1; + if (opts.shape === "circle") { + for (let p of pts) { + ctx.beginPath(); + ctx.arc(p[0], p[1], s, 0, TAU); + ctx[cmd](); + } + } else { + for (let p of pts) { + ctx[cmdR](p[0], p[1], s, s); + } + } +}; diff --git a/packages/hdom-canvas/src/draw/polygon.ts b/packages/hdom-canvas/src/draw/polygon.ts new file mode 100644 index 0000000000..759e80a9db --- /dev/null +++ b/packages/hdom-canvas/src/draw/polygon.ts @@ -0,0 +1,33 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { endShape } from "./end-shape"; + +export const polygon = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pts: ReadonlyVec[] +) => { + if (pts.length < 2) return; + __drawPoly(ctx, pts); + ctx.closePath(); + endShape(ctx, attribs); +}; + +/** + * Shared internal helper for polygon & polyline fns. + * + * @param ctx + * @param pts + */ +export const __drawPoly = ( + ctx: CanvasRenderingContext2D, + pts: ReadonlyVec[] +) => { + let p: ReadonlyVec = pts[0]; + ctx.beginPath(); + ctx.moveTo(p[0], p[1]); + for (let i = 1, n = pts.length; i < n; i++) { + p = pts[i]; + ctx.lineTo(p[0], p[1]); + } +}; diff --git a/packages/hdom-canvas/src/draw/polyline.ts b/packages/hdom-canvas/src/draw/polyline.ts new file mode 100644 index 0000000000..0e05acf281 --- /dev/null +++ b/packages/hdom-canvas/src/draw/polyline.ts @@ -0,0 +1,13 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { __drawPoly } from "./polygon"; + +export const polyline = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pts: ReadonlyVec[] +) => { + if (pts.length < 2 || attribs.stroke == "none") return; + __drawPoly(ctx, pts); + ctx.stroke(); +}; diff --git a/packages/hdom-canvas/src/draw/rect.ts b/packages/hdom-canvas/src/draw/rect.ts new file mode 100644 index 0000000000..6a783e0888 --- /dev/null +++ b/packages/hdom-canvas/src/draw/rect.ts @@ -0,0 +1,36 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { path } from "./path"; + +export const rect = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pos: ReadonlyVec, + w: number, + h: number, + r = 0 +) => { + let v: any; + if (r > 0) { + r = Math.min(Math.min(w, h) / 2, r); + w -= 2 * r; + h -= 2 * r; + return path(ctx, attribs, [ + ["M", [pos[0] + r, pos[1]]], + ["h", w], + ["a", [r, 0], [r, r], r], + ["v", h], + ["a", [0, r], [-r, r], r], + ["h", -w], + ["a", [-r, 0], [-r, -r], r], + ["v", -h], + ["a", [0, -r], [r, -r], r] + ]); + } + if ((v = attribs.fill) && v !== "none") { + ctx.fillRect(pos[0], pos[1], w, h); + } + if ((v = attribs.stroke) && v !== "none") { + ctx.strokeRect(pos[0], pos[1], w, h); + } +}; diff --git a/packages/hdom-canvas/src/draw/text.ts b/packages/hdom-canvas/src/draw/text.ts new file mode 100644 index 0000000000..e959435b0c --- /dev/null +++ b/packages/hdom-canvas/src/draw/text.ts @@ -0,0 +1,18 @@ +import { IObjectOf } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; + +export const text = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf, + pos: ReadonlyVec, + body: any, + maxWidth?: number +) => { + let v: any; + if ((v = attribs.fill) && v !== "none") { + ctx.fillText(body.toString(), pos[0], pos[1], maxWidth); + } + if ((v = attribs.stroke) && v !== "none") { + ctx.strokeText(body.toString(), pos[0], pos[1], maxWidth); + } +}; diff --git a/packages/hdom-canvas/src/impl.ts b/packages/hdom-canvas/src/impl.ts new file mode 100644 index 0000000000..45dde7330b --- /dev/null +++ b/packages/hdom-canvas/src/impl.ts @@ -0,0 +1,183 @@ +import { assert, NO_OP } from "@thi.ng/api"; +import { isArray, isNotStringAndIterable } from "@thi.ng/checks"; +import { diffArray, DiffMode } from "@thi.ng/diff"; +import { + equiv, + HDOMImplementation, + HDOMOpts, + releaseTree +} from "@thi.ng/hdom"; +import { walk } from "./walk"; + +const FN = "function"; +const STR = "string"; + +/** + * Special HTML5 canvas component which injects a branch-local hdom + * implementation for virtual SVG-like shape components / elements. + * These elements are then translated into canvas draw commands during + * the hdom update process. + * + * The canvas component automatically adjusts its size for HDPI displays + * by adding CSS `width` & `height` properties and pre-scaling the + * drawing context accordingly before shapes are processed. + * + * Shape components are expressed in standard hiccup syntax, however + * with the following restrictions: + * + * - Shape component objects with life cycle methods are only partially + * supported, i.e. only the `render` & `release` methods are used + * (Note, for performance reasons `release` methods are ignored by + * default. If your shape tree contains stateful components which use + * the `release` life cycle method, you'll need to explicitly enable + * the canvas component's `__release` attribute by setting it to + * `true`). + * - Currently no event listeners can be assigned to shapes (ignored), + * though this is planned for a future version. The canvas element + * itself can of course have event handlers as usual. + * + * All embedded component functions receive the user context object just + * like normal hdom components. + * + * For best performance, it's recommended to ensure all resulting shapes + * elements are provided in already normalized hiccup format (i.e. + * `[tag, {attribs}, ...]`). That way the `__normalize: false` control + * attribute can be added either to the canvas component itself (or to + * individual shapes / groups), and if present, will skip normalization + * of all children. + * + * @param _ hdom user context (ignored) + * @param attribs canvas attribs + * @param shapes shape components + */ +export const canvas = { + render(_: any, attribs: any, ...body: any[]) { + const cattribs = { ...attribs }; + delete cattribs.__diff; + delete cattribs.__normalize; + const dpr = window.devicePixelRatio || 1; + if (dpr !== 1) { + !cattribs.style && (cattribs.style = {}); + cattribs.style.width = `${cattribs.width}px`; + cattribs.style.height = `${cattribs.height}px`; + cattribs.width *= dpr; + cattribs.height *= dpr; + } + return [ + "canvas", + cattribs, + [ + "g", + { + __impl: IMPL, + __diff: attribs.__diff !== false, + __normalize: attribs.__normalize !== false, + __release: attribs.__release === true, + __serialize: false, + __clear: attribs.__clear, + scale: dpr !== 1 ? dpr : null + }, + ...body + ] + ]; + } +}; + +export const createTree = ( + _: Partial, + canvas: HTMLCanvasElement, + tree: any +) => { + // console.log(Date.now(), "draw"); + const ctx = canvas.getContext("2d"); + assert(!!ctx, "canvas ctx unavailable"); + const attribs = tree[1]; + if (attribs) { + if (attribs.__skip) return; + if (attribs.__clear !== false) { + ctx!.clearRect(0, 0, canvas.width, canvas.height); + } + } + walk(ctx!, tree, { attribs: {}, edits: [] }); +}; + +export const normalizeTree = (opts: Partial, tree: any): any => { + if (tree == null) { + return tree; + } + if (isArray(tree)) { + const tag = tree[0]; + if (typeof tag === FN) { + return normalizeTree( + opts, + tag.apply(null, [opts.ctx, ...tree.slice(1)]) + ); + } + if (typeof tag === STR) { + const attribs = tree[1]; + if (attribs && attribs.__normalize === false) { + return tree; + } + const res = [tree[0], attribs]; + for (let i = 2, n = tree.length; i < n; i++) { + const n = normalizeTree(opts, tree[i]); + n != null && res.push(n); + } + return res; + } + } else if (typeof tree === FN) { + return normalizeTree(opts, tree(opts.ctx)); + } else if (typeof tree.toHiccup === FN) { + return normalizeTree(opts, tree.toHiccup(opts.ctx)); + } else if (typeof tree.deref === FN) { + return normalizeTree(opts, tree.deref()); + } else if (isNotStringAndIterable(tree)) { + const res = []; + for (let t of tree) { + const n = normalizeTree(opts, t); + n != null && res.push(n); + } + return res; + } + return tree; +}; + +export const diffTree = ( + opts: Partial, + parent: HTMLCanvasElement, + prev: any[], + curr: any[], + child: number +) => { + const attribs = curr[1]; + if (attribs.__skip) return; + if (attribs.__diff === false) { + releaseTree(prev); + return createTree(opts, parent, curr); + } + // delegate to branch-local implementation + let impl: HDOMImplementation = attribs.__impl; + if (impl && impl !== IMPL) { + return impl.diffTree(opts, parent, prev, curr, child); + } + const delta = diffArray(prev, curr, DiffMode.ONLY_DISTANCE, equiv); + if (delta.distance > 0) { + return createTree(opts, parent, curr); + } +}; + +export const IMPL: HDOMImplementation = { + createTree, + normalizeTree, + diffTree, + hydrateTree: NO_OP, + getElementById: NO_OP, + createElement: NO_OP, + createTextElement: NO_OP, + replaceChild: NO_OP, + getChild: NO_OP, + removeAttribs: NO_OP, + removeChild: NO_OP, + setAttrib: NO_OP, + setContent: NO_OP +}; diff --git a/packages/hdom-canvas/src/index.ts b/packages/hdom-canvas/src/index.ts index 09bdadb1eb..fc085bf774 100644 --- a/packages/hdom-canvas/src/index.ts +++ b/packages/hdom-canvas/src/index.ts @@ -1,798 +1,17 @@ -import { assert, IObjectOf, NO_OP } from "@thi.ng/api"; -import { - isArray, - isArrayLike, - isNotStringAndIterable, - isNumber, - isString -} from "@thi.ng/checks"; -import { asCSS, ColorMode, ReadonlyColor } from "@thi.ng/color"; -import { diffArray, DiffMode } from "@thi.ng/diff"; -import { - equiv, - HDOMImplementation, - HDOMOpts, - releaseTree -} from "@thi.ng/hdom"; - -interface DrawState { - attribs: IObjectOf; - grads?: IObjectOf; - edits?: string[]; - restore?: boolean; -} - -type ReadonlyVec = ArrayLike & Iterable; - -const TAU = Math.PI * 2; - -const FN = "function"; -const STR = "string"; - -const DEFAULTS: any = { - align: "left", - alpha: 1, - baseline: "alphabetic", - comp: "source-over", - dash: [], - dashOffset: 0, - direction: "inherit", - fill: "#000", - filter: "none", - font: "10px sans-serif", - lineCap: "butt", - lineJoin: "miter", - miterLimit: 10, - shadowBlur: 0, - shadowColor: "rgba(0,0,0,0)", - shadowX: 0, - shadowY: 0, - smooth: true, - stroke: "#000" -}; - -const CTX_ATTRIBS: IObjectOf = { - align: "textAlign", - alpha: "globalAlpha", - baseline: "textBaseline", - clip: "clip", - compose: "globalCompositeOperation", - dash: "setLineDash", - dashOffset: "lineDashOffset", - direction: "direction", - fill: "fillStyle", - filter: "filter", - font: "font", - lineCap: "lineCap", - lineJoin: "lineJoin", - miterLimit: "miterLimit", - shadowBlur: "shadowBlur", - shadowColor: "shadowColor", - shadowX: "shadowOffsetX", - shadowY: "shadowOffsetY", - smooth: "imageSmoothingEnabled", - stroke: "strokeStyle", - weight: "lineWidth" -}; - -/** - * Special HTML5 canvas component which injects a branch-local hdom - * implementation for virtual SVG-like shape components / elements. - * These elements are then translated into canvas draw commands during - * the hdom update process. - * - * The canvas component automatically adjusts its size for HDPI displays - * by adding CSS `width` & `height` properties and pre-scaling the - * drawing context accordingly before shapes are processed. - * - * Shape components are expressed in standard hiccup syntax, however - * with the following restrictions: - * - * - Shape component objects with life cycle methods are only partially - * supported, i.e. only the `render` & `release` methods are used - * (Note, for performance reasons `release` methods are ignored by - * default. If your shape tree contains stateful components which use - * the `release` life cycle method, you'll need to explicitly enable - * the canvas component's `__release` attribute by setting it to - * `true`). - * - Currently no event listeners can be assigned to shapes (ignored), - * though this is planned for a future version. The canvas element - * itself can of course have event handlers as usual. - * - * All embedded component functions receive the user context object just - * like normal hdom components. - * - * For best performance, it's recommended to ensure all resulting shapes - * elements are provided in already normalized hiccup format (i.e. - * `[tag, {attribs}, ...]`). That way the `__normalize: false` control - * attribute can be added either to the canvas component itself (or to - * individual shapes / groups), and if present, will skip normalization - * of all children. - * - * @param _ hdom user context (ignored) - * @param attribs canvas attribs - * @param shapes shape components - */ -export const canvas = { - render(_: any, attribs: any, ...body: any[]) { - const cattribs = { ...attribs }; - delete cattribs.__diff; - delete cattribs.__normalize; - const dpr = window.devicePixelRatio || 1; - if (dpr !== 1) { - !cattribs.style && (cattribs.style = {}); - cattribs.style.width = `${cattribs.width}px`; - cattribs.style.height = `${cattribs.height}px`; - cattribs.width *= dpr; - cattribs.height *= dpr; - } - return [ - "canvas", - cattribs, - [ - "g", - { - __impl: IMPL, - __diff: attribs.__diff !== false, - __normalize: attribs.__normalize !== false, - __release: attribs.__release === true, - __serialize: false, - __clear: attribs.__clear, - scale: dpr !== 1 ? dpr : null - }, - ...body - ] - ]; - } -}; - -export const createTree = ( - _: Partial, - canvas: HTMLCanvasElement, - tree: any -) => { - // console.log(Date.now(), "draw"); - const ctx = canvas.getContext("2d"); - assert(!!ctx, "canvas ctx unavailable"); - const attribs = tree[1]; - if (attribs) { - if (attribs.__skip) return; - if (attribs.__clear !== false) { - ctx!.clearRect(0, 0, canvas.width, canvas.height); - } - } - walk(ctx!, tree, { attribs: {} }); -}; - -export const normalizeTree = (opts: Partial, tree: any): any => { - if (tree == null) { - return tree; - } - if (isArray(tree)) { - const tag = tree[0]; - if (typeof tag === FN) { - return normalizeTree( - opts, - tag.apply(null, [opts.ctx, ...tree.slice(1)]) - ); - } - if (typeof tag === STR) { - const attribs = tree[1]; - if (attribs && attribs.__normalize === false) { - return tree; - } - const res = [tree[0], attribs]; - for (let i = 2, n = tree.length; i < n; i++) { - const n = normalizeTree(opts, tree[i]); - n != null && res.push(n); - } - return res; - } - } else if (typeof tree === FN) { - return normalizeTree(opts, tree(opts.ctx)); - } else if (typeof tree.toHiccup === FN) { - return normalizeTree(opts, tree.toHiccup(opts.ctx)); - } else if (typeof tree.deref === FN) { - return normalizeTree(opts, tree.deref()); - } else if (isNotStringAndIterable(tree)) { - const res = []; - for (let t of tree) { - const n = normalizeTree(opts, t); - n != null && res.push(n); - } - return res; - } - return tree; -}; - -export const diffTree = ( - opts: Partial, - parent: HTMLCanvasElement, - prev: any[], - curr: any[], - child: number -) => { - const attribs = curr[1]; - if (attribs.__skip) return; - if (attribs.__diff === false) { - releaseTree(prev); - return createTree(opts, parent, curr); - } - // delegate to branch-local implementation - let impl: HDOMImplementation = attribs.__impl; - if (impl && impl !== IMPL) { - return impl.diffTree(opts, parent, prev, curr, child); - } - const delta = diffArray(prev, curr, DiffMode.ONLY_DISTANCE, equiv); - if (delta.distance > 0) { - return createTree(opts, parent, curr); - } -}; - -export const IMPL: HDOMImplementation = { - createTree, - normalizeTree, - diffTree, - hydrateTree: NO_OP, - getElementById: NO_OP, - createElement: NO_OP, - createTextElement: NO_OP, - replaceChild: NO_OP, - getChild: NO_OP, - removeAttribs: NO_OP, - removeChild: NO_OP, - setAttrib: NO_OP, - setContent: NO_OP -}; - -const walk = ( - ctx: CanvasRenderingContext2D, - shape: any[], - pstate: DrawState -) => { - if (!shape) return; - if (isArray(shape[0])) { - for (let s of shape) { - walk(ctx, s, pstate); - } - return; - } - const state = mergeState(ctx, pstate, shape[1]); - const attribs = state ? state.attribs : pstate.attribs; - if (attribs.__skip) return; - switch (shape[0]) { - case "g": - case "defs": - for ( - let i = 2, - n = shape.length, - __state = shape[0] === "g" ? state || pstate : pstate; - i < n; - i++ - ) { - walk(ctx, shape[i], __state); - } - break; - case "linearGradient": - defLinearGradient(ctx, pstate, shape[1], shape[2]); - break; - case "radialGradient": - defRadialGradient(ctx, pstate, shape[1], shape[2]); - break; - case "points": - points(ctx, attribs, shape[1], shape[2]); - break; - case "line": - line(ctx, attribs, shape[2], shape[3]); - break; - case "hline": - line(ctx, attribs, [-1e6, shape[2]], [1e6, shape[2]]); - break; - case "vline": - line(ctx, attribs, [shape[2], -1e6], [shape[2], 1e6]); - break; - case "polyline": - polyline(ctx, attribs, shape[2]); - break; - case "polygon": - polygon(ctx, attribs, shape[2]); - break; - case "path": - path(ctx, attribs, shape[2]); - break; - case "rect": - rect(ctx, attribs, shape[2], shape[3], shape[4], shape[5]); - break; - case "circle": - circularArc(ctx, attribs, shape[2], shape[3]); - break; - case "ellipse": - ellipticArc( - ctx, - attribs, - shape[2], - shape[3], - shape[4], - shape[5], - shape[6] - ); - break; - case "arc": - circularArc(ctx, attribs, shape[2], shape[3], shape[4], shape[5]); - break; - case "text": - text(ctx, attribs, shape[2], shape[3], shape[4]); - break; - case "img": - image( - ctx, - attribs, - shape[1], - shape[2], - shape[3], - shape[4], - shape[5] - ); - default: - } - state && restoreState(ctx, pstate, state); -}; - -const mergeState = ( - ctx: CanvasRenderingContext2D, - state: DrawState, - attribs: IObjectOf -) => { - let res: DrawState | undefined; - if (!attribs) return; - if (applyTransform(ctx, attribs)) { - res = { - attribs: { ...state.attribs }, - grads: { ...state.grads }, - edits: [], - restore: true - }; - } - for (let id in attribs) { - const k = CTX_ATTRIBS[id]; - if (k) { - const v = attribs[id]; - if (v != null && state.attribs[id] !== v) { - if (!res) { - res = { - attribs: { ...state.attribs }, - grads: { ...state.grads }, - edits: [] - }; - } - res!.attribs[id] = v; - res!.edits!.push(id); - setAttrib(ctx, state, id, k, v); - } - } - } - return res; -}; - -const restoreState = ( - ctx: CanvasRenderingContext2D, - prev: DrawState, - curr: DrawState -) => { - if (curr.restore) { - ctx.restore(); - return; - } - const edits = curr.edits; - if (edits) { - for (let attribs = prev.attribs, i = edits.length - 1; i >= 0; i--) { - const id = edits[i]; - const v = attribs[id]; - setAttrib( - ctx, - prev, - id, - CTX_ATTRIBS[id], - v != null ? v : DEFAULTS[id] - ); - } - } -}; - -const setAttrib = ( - ctx: CanvasRenderingContext2D, - state: DrawState, - id: string, - k: string, - val: any -) => { - switch (id) { - case "fill": - case "stroke": - case "shadowColor": - (ctx)[k] = resolveColor(state, val); - break; - case "dash": - (ctx)[k].call(ctx, val); - break; - case "clip": - break; - default: - (ctx)[k] = val; - } -}; - -const resolveColor = (state: DrawState, v: any) => - isString(v) - ? v[0] === "$" - ? state.grads![v.substr(1)] - : v - : isArrayLike(v) - ? isNumber((v).mode) - ? asCSS(v) - : asCSS(v, ColorMode.RGBA) - : isNumber(v) - ? asCSS(v, ColorMode.INT32) - : v; - -const applyTransform = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf -) => { - let v: any; - if ( - (v = attribs.transform) || - attribs.setTransform || - attribs.translate || - attribs.scale || - attribs.rotate - ) { - ctx.save(); - if (v) { - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - } else if ((v = attribs.setTransform)) { - ctx.setTransform(v[0], v[1], v[2], v[3], v[4], v[5]); - } else { - (v = attribs.translate) && ctx.translate(v[0], v[1]); - (v = attribs.rotate) && ctx.rotate(v); - (v = attribs.scale) && - (isArrayLike(v) ? ctx.scale(v[0], v[1]) : ctx.scale(v, v)); - } - return true; - } - return false; -}; - -const endShape = (ctx: CanvasRenderingContext2D, attribs: IObjectOf) => { - let v: any; - if ((v = attribs.fill) && v !== "none") { - ctx.fill(); - } - if ((v = attribs.stroke) && v !== "none") { - ctx.stroke(); - } -}; - -const defLinearGradient = ( - ctx: CanvasRenderingContext2D, - state: DrawState, - { id, from, to }: any, - stops: any[][] -) => { - const g = ctx.createLinearGradient(from[0], from[1], to[0], to[1]); - for (let s of stops) { - g.addColorStop(s[0], resolveColor(state, s[1])); - } - !state.grads && (state.grads = {}); - state.grads[id] = g; -}; - -const defRadialGradient = ( - ctx: CanvasRenderingContext2D, - state: DrawState, - { id, from, to, r1, r2 }: any, - stops: any[][] -) => { - const g = ctx.createRadialGradient(from[0], from[1], r1, to[0], to[1], r2); - for (let s of stops) { - g.addColorStop(s[0], resolveColor(state, s[1])); - } - !state.grads && (state.grads = {}); - state.grads[id] = g; -}; - -const line = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - a: ReadonlyVec, - b: ReadonlyVec -) => { - if (attribs.stroke === "none") return; - ctx.beginPath(); - ctx.moveTo(a[0], a[1]); - ctx.lineTo(b[0], b[1]); - ctx.stroke(); -}; - -const polyline = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pts: ReadonlyVec[] -) => { - if (pts.length < 2 || attribs.stroke == "none") return; - let p: ReadonlyVec = pts[0]; - ctx.beginPath(); - ctx.moveTo(p[0], p[1]); - for (let i = 1, n = pts.length; i < n; i++) { - p = pts[i]; - ctx.lineTo(p[0], p[1]); - } - ctx.stroke(); -}; - -const polygon = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pts: ReadonlyVec[] -) => { - if (pts.length < 2) return; - let p: ReadonlyVec = pts[0]; - ctx.beginPath(); - ctx.moveTo(p[0], p[1]); - for (let i = 1, n = pts.length; i < n; i++) { - p = pts[i]; - ctx.lineTo(p[0], p[1]); - } - ctx.closePath(); - endShape(ctx, attribs); -}; - -const path = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - segments: any[] -) => { - ctx.beginPath(); - let a: ReadonlyVec = [0, 0]; - for (let i = 0, n = segments.length; i < n; i++) { - const s = segments[i]; - let b = s[1], - c, - d; - switch (s[0]) { - // move to - case "m": - b = [a[0] + b[0], a[1] + b[1]]; - case "M": - ctx.moveTo(b[0], b[1]); - a = b; - break; - // line to - case "l": - b = [a[0] + b[0], a[1] + b[1]]; - case "L": - ctx.lineTo(b[0], b[1]); - a = b; - break; - // horizontal line rel - case "h": - b = [a[0] + b, a[1]]; - ctx.lineTo(b[0], b[1]); - a = b; - break; - // horizontal line abs - case "H": - b = [b, a[1]]; - ctx.lineTo(b[0], b[1]); - a = b; - break; - // vertical line rel - case "v": - b = [a[0], a[1] + b]; - ctx.lineTo(b[0], b[1]); - a = b; - break; - // vertical line abs - case "V": - b = [a[0], b]; - ctx.lineTo(b[0], b[1]); - a = b; - break; - // cubic curve rel - case "c": - c = s[2]; - d = s[3]; - d = [a[0] + d[0], a[1] + d[1]]; - ctx.bezierCurveTo( - a[0] + b[0], - a[1] + b[1], - a[0] + c[0], - a[1] + c[1], - d[0], - d[1] - ); - a = d; - break; - // cubic curve abs - case "C": - c = s[2]; - d = s[3]; - ctx.bezierCurveTo(b[0], b[1], c[0], c[1], d[0], d[1]); - a = d; - break; - // quadratic curve rel - case "q": - c = s[2]; - c = [a[0] + c[0], a[1] + c[1]]; - ctx.quadraticCurveTo(a[0] + b[0], a[1] + b[1], c[0], c[1]); - a = c; - break; - // quadratic curve abs - case "Q": - c = s[2]; - ctx.quadraticCurveTo(b[0], b[1], c[0], c[1]); - a = c; - break; - // circular arc rel - // Note: NOT compatible w/ SVG arc segments - case "a": - c = s[2]; - c = [a[0] + c[0], a[1] + c[1]]; - ctx.arcTo(a[0] + b[0], a[1] + b[1], c[0], c[1], s[3]); - a = c; - break; - // circular arc abs - // Note: NOT compatible w/ SVG arc segments - case "A": - c = s[2]; - ctx.arcTo(b[0], b[1], c[0], c[1], s[3]); - a = c; - break; - // close path - case "z": - case "Z": - ctx.closePath(); - } - } - endShape(ctx, attribs); -}; - -const circularArc = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pos: ReadonlyVec, - r: number, - start = 0, - end = TAU, - antiCCW = false -) => { - ctx.beginPath(); - ctx.arc(pos[0], pos[1], r, start, end, antiCCW); - endShape(ctx, attribs); -}; - -const ellipticArc = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pos: ReadonlyVec, - r: ReadonlyVec, - axis = 0, - start = 0, - end = TAU, - ccw = false -) => { - ctx.beginPath(); - ctx.ellipse(pos[0], pos[1], r[0], r[1], axis, start, end, ccw); - endShape(ctx, attribs); -}; - -const rect = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pos: ReadonlyVec, - w: number, - h: number, - r = 0 -) => { - let v: any; - if (r > 0) { - r = Math.min(Math.min(w, h) / 2, r); - w -= 2 * r; - h -= 2 * r; - return path(ctx, attribs, [ - ["M", [pos[0] + r, pos[1]]], - ["h", w], - ["a", [r, 0], [r, r], r], - ["v", h], - ["a", [0, r], [-r, r], r], - ["h", -w], - ["a", [-r, 0], [-r, -r], r], - ["v", -h], - ["a", [0, -r], [r, -r], r] - ]); - } - if ((v = attribs.fill) && v !== "none") { - ctx.fillRect(pos[0], pos[1], w, h); - } - if ((v = attribs.stroke) && v !== "none") { - ctx.strokeRect(pos[0], pos[1], w, h); - } -}; - -const points = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - opts: IObjectOf, - pts: Iterable -) => { - const s = (opts && opts.size) || 1; - let v: any; - if ((v = attribs.fill) && v !== "none") { - if (opts.shape === "circle") { - for (let p of pts) { - ctx.beginPath(); - ctx.arc(p[0], p[1], s, 0, TAU); - ctx.fill(); - } - } else { - for (let p of pts) { - ctx.fillRect(p[0], p[1], s, s); - } - } - } - if ((v = attribs.stroke) && v !== "none") { - if (opts.shape === "circle") { - for (let p of pts) { - ctx.beginPath(); - ctx.arc(p[0], p[1], s, 0, TAU); - ctx.stroke(); - } - } else { - for (let p of pts) { - ctx.strokeRect(p[0], p[1], s, s); - } - } - } -}; - -const text = ( - ctx: CanvasRenderingContext2D, - attribs: IObjectOf, - pos: ReadonlyVec, - body: any, - maxWidth?: number -) => { - let v: any; - if ((v = attribs.fill) && v !== "none") { - ctx.fillText(body.toString(), pos[0], pos[1], maxWidth); - } - if ((v = attribs.stroke) && v !== "none") { - ctx.strokeText(body.toString(), pos[0], pos[1], maxWidth); - } -}; - -const image = ( - ctx: CanvasRenderingContext2D, - _: IObjectOf, - { width, height }: IObjectOf, - img: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap, - dpos: ReadonlyVec, - spos?: ReadonlyVec, - ssize?: ReadonlyVec -) => { - width = width || img.width; - height = height || img.height; - spos - ? ctx.drawImage( - img, - spos[0], - spos[1], - ssize ? ssize[0] : width, - ssize ? ssize[1] : height, - dpos[0], - dpos[1], - width, - height - ) - : ctx.drawImage(img, dpos[0], dpos[1], width, height); -}; +export * from "./api"; +export * from "./impl"; +export * from "./state"; +export * from "./walk"; + +export * from "./draw/arc"; +export * from "./draw/color"; +export * from "./draw/end-shape"; +export * from "./draw/image"; +export * from "./draw/line"; +export * from "./draw/packed-points"; +export * from "./draw/path"; +export * from "./draw/points"; +export * from "./draw/polygon"; +export * from "./draw/polyline"; +export * from "./draw/rect"; +export * from "./draw/text"; diff --git a/packages/hdom-canvas/src/state.ts b/packages/hdom-canvas/src/state.ts new file mode 100644 index 0000000000..91f5f0f183 --- /dev/null +++ b/packages/hdom-canvas/src/state.ts @@ -0,0 +1,161 @@ +import { IObjectOf } from "@thi.ng/api"; +import { isArrayLike } from "@thi.ng/checks"; +import { DrawState } from "./api"; +import { resolveGradientOrColor } from "./draw/color"; + +const DEFAULTS: any = { + align: "left", + alpha: 1, + baseline: "alphabetic", + compose: "source-over", + dash: [], + dashOffset: 0, + direction: "inherit", + fill: "#000", + filter: "none", + font: "10px sans-serif", + lineCap: "butt", + lineJoin: "miter", + miterLimit: 10, + shadowBlur: 0, + shadowColor: "rgba(0,0,0,0)", + shadowX: 0, + shadowY: 0, + smooth: true, + stroke: "#000", + weight: 1 +}; + +const CTX_ATTRIBS: IObjectOf = { + align: "textAlign", + alpha: "globalAlpha", + baseline: "textBaseline", + clip: "clip", + compose: "globalCompositeOperation", + dash: "setLineDash", + dashOffset: "lineDashOffset", + direction: "direction", + fill: "fillStyle", + filter: "filter", + font: "font", + lineCap: "lineCap", + lineJoin: "lineJoin", + miterLimit: "miterLimit", + shadowBlur: "shadowBlur", + shadowColor: "shadowColor", + shadowX: "shadowOffsetX", + shadowY: "shadowOffsetY", + smooth: "imageSmoothingEnabled", + stroke: "strokeStyle", + weight: "lineWidth" +}; + +const newState = (state: DrawState, restore = false) => ({ + attribs: { ...state.attribs }, + grads: { ...state.grads }, + edits: [], + restore +}); + +export const mergeState = ( + ctx: CanvasRenderingContext2D, + state: DrawState, + attribs: IObjectOf +) => { + let res: DrawState | undefined; + if (!attribs) return; + if (applyTransform(ctx, attribs)) { + res = newState(state, true); + } + for (let id in attribs) { + const k = CTX_ATTRIBS[id]; + if (k) { + const v = attribs[id]; + if (v != null && state.attribs[id] !== v) { + !res && (res = newState(state)); + res.attribs[id] = v; + res.edits!.push(id); + setAttrib(ctx, state, id, k, v); + } + } + } + return res; +}; + +export const restoreState = ( + ctx: CanvasRenderingContext2D, + prev: DrawState, + curr: DrawState +) => { + if (curr.restore) { + ctx.restore(); + return; + } + const edits = curr.edits; + const attribs = prev.attribs; + for (let i = edits.length; --i >= 0; ) { + const id = edits[i]; + const v = attribs[id]; + setAttrib(ctx, prev, id, CTX_ATTRIBS[id], v != null ? v : DEFAULTS[id]); + } +}; + +export const registerGradient = ( + state: DrawState, + id: string, + g: CanvasGradient +) => { + !state.grads && (state.grads = {}); + state.grads[id] = g; +}; + +const setAttrib = ( + ctx: CanvasRenderingContext2D, + state: DrawState, + id: string, + k: string, + val: any +) => { + switch (id) { + case "fill": + case "stroke": + case "shadowColor": + (ctx)[k] = resolveGradientOrColor(state, val); + break; + case "dash": + (ctx)[k].call(ctx, val); + break; + case "clip": + break; + default: + (ctx)[k] = val; + } +}; + +const applyTransform = ( + ctx: CanvasRenderingContext2D, + attribs: IObjectOf +) => { + let v: any; + if ( + (v = attribs.transform) || + attribs.setTransform || + attribs.translate || + attribs.scale || + attribs.rotate + ) { + ctx.save(); + if (v) { + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + } else if ((v = attribs.setTransform)) { + ctx.setTransform(v[0], v[1], v[2], v[3], v[4], v[5]); + } else { + (v = attribs.translate) && ctx.translate(v[0], v[1]); + (v = attribs.rotate) && ctx.rotate(v); + (v = attribs.scale) && + (isArrayLike(v) ? ctx.scale(v[0], v[1]) : ctx.scale(v, v)); + } + return true; + } + return false; +}; diff --git a/packages/hdom-canvas/src/walk.ts b/packages/hdom-canvas/src/walk.ts new file mode 100644 index 0000000000..f93baa27a3 --- /dev/null +++ b/packages/hdom-canvas/src/walk.ts @@ -0,0 +1,123 @@ +import { isArray } from "@thi.ng/checks"; +import { DrawState } from "./api"; +import { circularArc, ellipticArc } from "./draw/arc"; +import { defLinearGradient, defRadialGradient } from "./draw/color"; +import { image } from "./draw/image"; +import { line } from "./draw/line"; +import { packedPoints } from "./draw/packed-points"; +import { path } from "./draw/path"; +import { points } from "./draw/points"; +import { polygon } from "./draw/polygon"; +import { polyline } from "./draw/polyline"; +import { rect } from "./draw/rect"; +import { text } from "./draw/text"; +import { mergeState, registerGradient, restoreState } from "./state"; + +export const walk = ( + ctx: CanvasRenderingContext2D, + shape: any[], + pstate: DrawState +) => { + if (!shape) return; + if (isArray(shape[0])) { + for (let s of shape) { + walk(ctx, s, pstate); + } + return; + } + const state = mergeState(ctx, pstate, shape[1]); + const attribs = state ? state.attribs : pstate.attribs; + if (attribs.__skip) return; + switch (shape[0]) { + case "g": + case "defs": + defs(ctx, state, pstate, shape); + break; + case "linearGradient": + registerGradient( + pstate, + shape[1].id, + defLinearGradient(ctx, shape[1], shape[2]) + ); + break; + case "radialGradient": + registerGradient( + pstate, + shape[1].id, + defRadialGradient(ctx, shape[1], shape[2]) + ); + break; + case "points": + points(ctx, attribs, shape[1], shape[2]); + break; + case "packedPoints": + packedPoints(ctx, attribs, shape[1], shape[2]); + break; + case "line": + line(ctx, attribs, shape[2], shape[3]); + break; + case "hline": + line(ctx, attribs, [-1e6, shape[2]], [1e6, shape[2]]); + break; + case "vline": + line(ctx, attribs, [shape[2], -1e6], [shape[2], 1e6]); + break; + case "polyline": + polyline(ctx, attribs, shape[2]); + break; + case "polygon": + polygon(ctx, attribs, shape[2]); + break; + case "path": + path(ctx, attribs, shape[2]); + break; + case "rect": + rect(ctx, attribs, shape[2], shape[3], shape[4], shape[5]); + break; + case "circle": + circularArc(ctx, attribs, shape[2], shape[3]); + break; + case "ellipse": + ellipticArc( + ctx, + attribs, + shape[2], + shape[3], + shape[4], + shape[5], + shape[6] + ); + break; + case "arc": + circularArc(ctx, attribs, shape[2], shape[3], shape[4], shape[5]); + break; + case "text": + text(ctx, attribs, shape[2], shape[3], shape[4]); + break; + case "img": + image( + ctx, + attribs, + shape[1], + shape[2], + shape[3], + shape[4], + shape[5] + ); + default: + } + state && restoreState(ctx, pstate, state); +}; + +const defs = ( + ctx: CanvasRenderingContext2D, + state: DrawState | undefined, + pstate: DrawState, + shape: any[] +) => { + const n = shape.length; + const __state = shape[0] === "g" ? state || pstate : pstate; + for (let i = 2; i < n; i++) { + walk(ctx, shape[i], __state); + } +}; diff --git a/packages/hdom-components/CHANGELOG.md b/packages/hdom-components/CHANGELOG.md index e529b170de..494e51041a 100644 --- a/packages/hdom-components/CHANGELOG.md +++ b/packages/hdom-components/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@3.1.5...@thi.ng/hdom-components@3.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/hdom-components + + + + + +## [3.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@3.1.4...@thi.ng/hdom-components@3.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/hdom-components + + + + + +## [3.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@3.1.3...@thi.ng/hdom-components@3.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hdom-components + + + + + +## [3.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@3.1.2...@thi.ng/hdom-components@3.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/hdom-components + + + + + ## [3.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@3.1.1...@thi.ng/hdom-components@3.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/hdom-components diff --git a/packages/hdom-components/package.json b/packages/hdom-components/package.json index 99db980097..6a74ed96b1 100644 --- a/packages/hdom-components/package.json +++ b/packages/hdom-components/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom-components", - "version": "3.1.2", + "version": "3.1.6", "description": "Raw, skinnable UI & SVG components for @thi.ng/hdom", "module": "./index.js", "main": "./lib/index.js", @@ -29,16 +29,15 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/math": "^1.4.2", - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/transducers-stats": "^1.1.2", - "@types/webgl2": "^0.0.5" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/math": "^1.5.0", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/transducers-stats": "^1.1.6" }, "keywords": [ "ES6", diff --git a/packages/hdom-mock/CHANGELOG.md b/packages/hdom-mock/CHANGELOG.md index 1bca966451..d8ac510fcb 100644 --- a/packages/hdom-mock/CHANGELOG.md +++ b/packages/hdom-mock/CHANGELOG.md @@ -3,6 +3,46 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-mock@1.1.6...@thi.ng/hdom-mock@1.1.7) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/hdom-mock + + + + + +## [1.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-mock@1.1.5...@thi.ng/hdom-mock@1.1.6) (2019-09-23) + +**Note:** Version bump only for package @thi.ng/hdom-mock + + + + + +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-mock@1.1.4...@thi.ng/hdom-mock@1.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/hdom-mock + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-mock@1.1.3...@thi.ng/hdom-mock@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hdom-mock + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-mock@1.1.2...@thi.ng/hdom-mock@1.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/hdom-mock + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-mock@1.1.1...@thi.ng/hdom-mock@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/hdom-mock diff --git a/packages/hdom-mock/package.json b/packages/hdom-mock/package.json index 0bd4842da2..3ddcd7e52c 100644 --- a/packages/hdom-mock/package.json +++ b/packages/hdom-mock/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom-mock", - "version": "1.1.2", + "version": "1.1.7", "description": "Mock base implementation for @thi.ng/hdom API", "module": "./index.js", "main": "./lib/index.js", @@ -29,13 +29,13 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/hdom": "^8.0.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/hdom": "^8.0.7" }, "keywords": [ "ES6", diff --git a/packages/hdom/CHANGELOG.md b/packages/hdom/CHANGELOG.md index 260f60e3a6..546b71c2e9 100644 --- a/packages/hdom/CHANGELOG.md +++ b/packages/hdom/CHANGELOG.md @@ -3,6 +3,52 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.0.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@8.0.6...@thi.ng/hdom@8.0.7) (2019-11-09) + + +### Bug Fixes + +* **hdom:** fix [#72](https://github.com/thi-ng/umbrella/issues/72), update __skip diff handling & HDOMImplementation ([0071df3](https://github.com/thi-ng/umbrella/commit/0071df3c770d6f9de10301853cbd6ecb06df83fb)) + + + + + +## [8.0.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@8.0.5...@thi.ng/hdom@8.0.6) (2019-09-23) + + +### Bug Fixes + +* **hdom:** fix [#133](https://github.com/thi-ng/umbrella/issues/133) boolean attrib handling, add more element properties ([c4bf94f](https://github.com/thi-ng/umbrella/commit/c4bf94f)) + + + + + +## [8.0.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@8.0.4...@thi.ng/hdom@8.0.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/hdom + + + + + +## [8.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@8.0.3...@thi.ng/hdom@8.0.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hdom + + + + + +## [8.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@8.0.2...@thi.ng/hdom@8.0.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/hdom + + + + + ## [8.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@8.0.1...@thi.ng/hdom@8.0.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/hdom diff --git a/packages/hdom/README.md b/packages/hdom/README.md index e582589682..9134a7c1ad 100644 --- a/packages/hdom/README.md +++ b/packages/hdom/README.md @@ -153,7 +153,7 @@ diffing via RAF). ```ts import { fromInterval, stream, sync } from "@thi.ng/rstream"; import { updateDOM } from "@thi.ng/transducers-hdom"; -import { map, scan } from "@thi.ng/transducers"; +import { map, scan, count } from "@thi.ng/transducers"; // root component function const app = ({ ticks, clicks }) => @@ -165,7 +165,7 @@ const app = ({ ticks, clicks }) => ]; // transformed stream to count clicks -const clickStream = stream().transform(scan(tx.count(-1))); +const clickStream = stream().transform(scan(count(-1))); // seed clickStream.next(0); @@ -322,7 +322,7 @@ previous DOM tree. - hdom can be used **without** diffing, i.e. for compact, one-off DOM creation (see [`renderOnce()`](#renderonce)) -![hdom dataflow](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/hdom-dataflow.png) +![hdom dataflow](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/hdom/hdom-dataflow.png) The syntax is inspired by Clojure's [Hiccup](https://github.com/weavejester/hiccup) and @@ -759,42 +759,42 @@ Non-exhaustive list: ### Realtime crypto candle chart -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/crypto-chart.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/crypto-chart.png) [Source](https://github.com/thi-ng/umbrella/tree/master/examples/crypto-chart) | [Live version](https://demo.thi.ng/umbrella/crypto-chart/) ### Git commit log table -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/commit-table-ssr.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/commit-table-ssr.png) [Source](https://github.com/thi-ng/umbrella/tree/master/examples/commit-table-ssr) | [Live version](https://demo.thi.ng/umbrella/commit-table-ssr/) ### XML/HTML/SVG to Hiccup converter -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/xml-converter.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/xml-converter.png) [Source](https://github.com/thi-ng/umbrella/tree/master/examples/xml-converter) | [Live version](https://demo.thi.ng/umbrella/xml-converter/) ### Interactive SVG grid generator -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/rstream-grid.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/rstream-grid.png) [Source](https://github.com/thi-ng/umbrella/tree/master/examples/rstream-grid) | [Live version](https://demo.thi.ng/umbrella/rstream-grid/) ### Interactive additive waveform visualization -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/svg-waveform.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/svg-waveform.png) [Source](https://github.com/thi-ng/umbrella/tree/master/examples/svg-waveform) | [Live version](https://demo.thi.ng/umbrella/svg-waveform/) ### Dataflow graph SVG components -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/estuary.jpg) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/estuary/estuary.jpg) This is a preview of the WIP [@thi.ng/estuary](https://github.com/thi-ng/umbrella/tree/feature/estuary/packages/estuary) @@ -805,14 +805,14 @@ package: ### Mouse gesture analysis -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/gesture-analysis.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/gesture-analysis.png) [Source](https://github.com/thi-ng/umbrella/tree/master/examples/gesture-analysis) | [Live version](https://demo.thi.ng/umbrella/gesture-analysis) ### Canvas based radial dial input widget -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/canvas-dial.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/canvas-dial.png) [Source](https://github.com/thi-ng/umbrella/tree/master/examples/canvas-dial) | [Live version](https://demo.thi.ng/umbrella/canvas-dial/) @@ -1223,6 +1223,10 @@ ignored during diffing. Therefore, if this attribute is enabled the element should either have no children OR the children are the same (type) as when the attribute is disabled (i.e. when `__skip` is falsy). +Furthermore, once a previously skipped element is re-enabled (i.e. its +`__skip` attrib is now falsy again), the element's entire sub-tree is +re-created, but any lifecycle `init()` methods will not be re-executed. + ### Benchmarks Some stress test benchmarks are here: diff --git a/packages/hdom/package.json b/packages/hdom/package.json index 93eedddad0..9ce8a05ca3 100644 --- a/packages/hdom/package.json +++ b/packages/hdom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom", - "version": "8.0.2", + "version": "8.0.7", "description": "Lightweight vanilla ES6 UI component trees with customizable branch-local behaviors", "module": "./index.js", "main": "./lib/index.js", @@ -25,21 +25,21 @@ "pub": "yarn build:release && yarn publish --access public" }, "devDependencies": { - "@thi.ng/atom": "^3.0.2", + "@thi.ng/atom": "^3.1.1", "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/diff": "^3.2.2", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/hiccup": "^3.2.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/diff": "^3.2.5", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/hiccup": "^3.2.6" }, "keywords": [ "browser", diff --git a/packages/hdom/src/api.ts b/packages/hdom/src/api.ts index 0e9abe9c86..0cd6970179 100644 --- a/packages/hdom/src/api.ts +++ b/packages/hdom/src/api.ts @@ -261,12 +261,14 @@ export interface HDOMImplementation { * @param parent * @param tree * @param child + * @param init */ createTree( opts: Partial, parent: T, tree: any, - child?: number + child?: number, + init?: boolean ): T | T[]; /** @@ -400,7 +402,8 @@ export interface HDOMImplementation { opts: Partial, parent: T, child: number, - newTree: any + newTree: any, + init?: boolean ): T | T[]; /** diff --git a/packages/hdom/src/default.ts b/packages/hdom/src/default.ts index 69a6f6d315..33bc4ad27b 100644 --- a/packages/hdom/src/default.ts +++ b/packages/hdom/src/default.ts @@ -18,8 +18,8 @@ import { normalizeTree } from "./normalize"; * Default target implementation to manipulate browser DOM. */ export const DEFAULT_IMPL: HDOMImplementation = { - createTree(opts, parent, tree, child?) { - return createTree(opts, this, parent, tree, child); + createTree(opts, parent, tree, child?, init?) { + return createTree(opts, this, parent, tree, child, init); }, hydrateTree(opts, parent, tree, child?) { @@ -42,8 +42,8 @@ export const DEFAULT_IMPL: HDOMImplementation = { createTextElement, - replaceChild(opts, parent, child, tree) { - replaceChild(opts, this, parent, child, tree); + replaceChild(opts, parent, child, tree, init?) { + replaceChild(opts, this, parent, child, tree, init); }, removeChild, diff --git a/packages/hdom/src/diff.ts b/packages/hdom/src/diff.ts index f8d9dbfc56..35c28a03b9 100644 --- a/packages/hdom/src/diff.ts +++ b/packages/hdom/src/diff.ts @@ -12,6 +12,11 @@ import { HDOMImplementation, HDOMOpts } from "./api"; const isArray = Array.isArray; const max = Math.max; +const OBJP = Object.getPrototypeOf({}); + +const FN = "function"; +const STR = "string"; + // child index tracking template buffer const INDEX = (() => { const res = new Array(2048); @@ -60,6 +65,11 @@ export const diffTree = ( impl.replaceChild(opts, parent, child, curr); return; } + const pattribs = prev[1]; + if (pattribs && pattribs.__skip) { + impl.replaceChild(opts, parent, child, curr, false); + return; + } // delegate to branch-local implementation let _impl = attribs.__impl; if (_impl && _impl !== impl) { @@ -73,10 +83,6 @@ export const diffTree = ( const el = impl.getChild(parent, child); let i: number; let ii: number; - let j: number; - let idx: number; - let k: any; - let eq: any[]; let status: number; let val: any; if (edits[0] !== 0 || prev[1].key !== attribs.key) { @@ -103,43 +109,29 @@ export const diffTree = ( status = edits[ii]; if (!status) continue; if (status === -1) { - // element removed / edited? - val = edits[ii + 2]; - if (isArray(val)) { - k = val[1].key; - if (k !== undefined && equivKeys[k][2] !== undefined) { - eq = equivKeys[k]; - k = eq[0]; - // LOGGER.fine(`diff equiv key @ ${k}:`, prev[k], curr[eq[2]]); - diffTree(opts, impl, el, prev[k], curr[eq[2]], offsets[k]); - } else { - idx = edits[ii + 1]; - // LOGGER.fine("remove @", offsets[idx], val); - releaseTree(val); - impl.removeChild(el, offsets[idx]); - for (j = prevLength; j > idx; j--) { - offsets[j] = max(offsets[j] - 1, 0); - } - } - } else if (typeof val === "string") { - impl.setContent(el, ""); - } + diffDeleted( + opts, + impl, + el, + prev, + curr, + edits, + ii, + equivKeys, + offsets, + prevLength + ); } else { - // element added/inserted? - val = edits[ii + 2]; - if (typeof val === "string") { - impl.setContent(el, val); - } else if (isArray(val)) { - k = val[1].key; - if (k === undefined || equivKeys[k][0] === undefined) { - idx = edits[ii + 1]; - // LOGGER.fine("insert @", offsets[idx], val); - impl.createTree(opts, el, val, offsets[idx]); - for (j = prevLength; j >= idx; j--) { - offsets[j]++; - } - } - } + diffAdded( + opts, + impl, + el, + edits, + ii, + equivKeys, + offsets, + prevLength + ); } } // call __init after all children have been added/updated @@ -148,6 +140,74 @@ export const diffTree = ( } }; +const diffDeleted = ( + opts: Partial, + impl: HDOMImplementation, + el: T, + prev: any[], + curr: any[], + edits: any[], + ii: number, + equivKeys: IObjectOf, + offsets: any[], + prevLength: number +) => { + const val = edits[ii + 2]; + if (isArray(val)) { + let k = val[1].key; + if (k !== undefined && equivKeys[k][2] !== undefined) { + const eq = equivKeys[k]; + k = eq[0]; + // LOGGER.fine(`diff equiv key @ ${k}:`, prev[k], curr[eq[2]]); + diffTree(opts, impl, el, prev[k], curr[eq[2]], offsets[k]); + } else { + const idx = edits[ii + 1]; + // LOGGER.fine("remove @", offsets[idx], val); + releaseTree(val); + impl.removeChild(el, offsets[idx]); + incOffsets(offsets, prevLength, idx); + } + } else if (typeof val === STR) { + impl.setContent(el, ""); + } +}; + +const diffAdded = ( + opts: Partial, + impl: HDOMImplementation, + el: T, + edits: any[], + ii: number, + equivKeys: IObjectOf, + offsets: any[], + prevLength: number +) => { + const val = edits[ii + 2]; + if (typeof val === STR) { + impl.setContent(el, val); + } else if (isArray(val)) { + const k = val[1].key; + if (k === undefined || equivKeys[k][0] === undefined) { + const idx = edits[ii + 1]; + // LOGGER.fine("insert @", offsets[idx], val); + impl.createTree(opts, el, val, offsets[idx]); + decOffsets(offsets, prevLength, idx); + } + } +}; + +const incOffsets = (offsets: any[], j: number, idx: number) => { + for (; j > idx; j--) { + offsets[j] = max(offsets[j] - 1, 0); + } +}; + +const decOffsets = (offsets: any[], j: number, idx: number) => { + for (; j >= idx; j--) { + offsets[j]++; + } +}; + /** * Helper function for `diffTree()` to compute & apply the difference * between a node's `prev` and `curr` attributes. @@ -169,26 +229,16 @@ export const diffAttributes = ( let i: number, e, edits; for (edits = delta.edits!, i = edits.length; (i -= 2) >= 0; ) { e = edits[i]; - if (e.indexOf("on") === 0) { - impl.removeAttribs(el, [e], prev); - } - if (e !== "value") { - impl.setAttrib(el, e, edits[i + 1], curr); - } else { - val = edits[i + 1]; - } + e.indexOf("on") === 0 && impl.removeAttribs(el, [e], prev); + e !== "value" + ? impl.setAttrib(el, e, edits[i + 1], curr) + : (val = edits[i + 1]); } for (edits = delta.adds!, i = edits.length; --i >= 0; ) { e = edits[i]; - if (e !== "value") { - impl.setAttrib(el, e, curr[e], curr); - } else { - val = curr[e]; - } - } - if (val !== SEMAPHORE) { - impl.setAttrib(el, "value", val, curr); + e !== "value" ? impl.setAttrib(el, e, curr[e], curr) : (val = curr[e]); } + val !== SEMAPHORE && impl.setAttrib(el, "value", val, curr); }; /** @@ -233,11 +283,6 @@ const extractEquivElements = (edits: any[]) => { return equiv; }; -const OBJP = Object.getPrototypeOf({}); - -const FN = "function"; -const STR = "string"; - /** * Customized version @thi.ng/equiv which takes `__diff` attributes into * account (at any nesting level). If an hdom element's attribute object diff --git a/packages/hdom/src/dom.ts b/packages/hdom/src/dom.ts index 281c058e85..593ca41729 100644 --- a/packages/hdom/src/dom.ts +++ b/packages/hdom/src/dom.ts @@ -6,6 +6,9 @@ import { HDOMImplementation, HDOMOpts } from "./api"; const isArray = isa; const isNotStringAndIterable = isi; +const maybeInitElement = (el: T, tree: any) => + tree.__init && tree.__init.apply(tree.__this, [el, ...tree.__args]); + /** * See `HDOMImplementation` interface for further details. * @@ -19,7 +22,8 @@ export const createTree = ( impl: HDOMImplementation, parent: T, tree: any, - insert?: number + insert?: number, + init = true ): any => { if (isArray(tree)) { const tag = tree[0]; @@ -38,28 +42,24 @@ export const createTree = ( opts, parent, tree, - insert + insert, + init ); } const el = impl.createElement(parent, tag, attribs, insert); if (tree.length > 2) { const n = tree.length; for (let i = 2; i < n; i++) { - createTree(opts, impl, el, tree[i]); + createTree(opts, impl, el, tree[i], undefined, init); } } - if ((tree).__init) { - (tree).__init.apply((tree).__this, [ - el, - ...(tree).__args - ]); - } + init && maybeInitElement(el, tree); return el; } if (isNotStringAndIterable(tree)) { const res = []; for (let t of tree) { - res.push(createTree(opts, impl, parent, t)); + res.push(createTree(opts, impl, parent, t, insert, init)); } return res; } @@ -104,16 +104,9 @@ export const hydrateTree = ( index ); } - if ((tree).__init) { - (tree).__init.apply((tree).__this, [ - el, - ...(tree).__args - ]); - } + maybeInitElement(el, tree); for (let a in attribs) { - if (a.indexOf("on") === 0) { - impl.setAttrib(el, a, attribs[a]); - } + a.indexOf("on") === 0 && impl.setAttrib(el, a, attribs[a]); } for (let n = tree.length, i = 2; i < n; i++) { hydrateTree(opts, impl, el, tree[i], i - 2); @@ -149,34 +142,22 @@ export const createElement = ( const el = SVG_TAGS[tag] ? document.createElementNS(SVG_NS, tag) : document.createElement(tag); - if (parent) { - if (insert == null) { - parent.appendChild(el); - } else { - parent.insertBefore(el, parent.children[insert]); - } - } - if (attribs) { - setAttribs(el, attribs); - } - return el; + attribs && setAttribs(el, attribs); + return addChild(parent, el, insert); }; export const createTextElement = ( parent: Element, content: string, insert?: number -) => { - const el = document.createTextNode(content); - if (parent) { - if (insert === undefined) { - parent.appendChild(el); - } else { - parent.insertBefore(el, parent.children[insert]); - } - } - return el; -}; +) => addChild(parent, document.createTextNode(content), insert); + +export const addChild = (parent: Element, child: Node, insert?: number) => + parent + ? insert === undefined + ? parent.appendChild(child) + : parent.insertBefore(child, parent.children[insert]) + : child; export const getChild = (parent: Element, child: number) => parent.children[child]; @@ -186,9 +167,11 @@ export const replaceChild = ( impl: HDOMImplementation, parent: Element, child: number, - tree: any + tree: any, + init = true ) => ( - impl.removeChild(parent, child), impl.createTree(opts, parent, tree, child) + impl.removeChild(parent, child), + impl.createTree(opts, parent, tree, child, init) ); export const cloneWithNewAttribs = (el: Element, attribs: any) => { @@ -242,19 +225,34 @@ export const setAttrib = (el: Element, id: string, val: any, attribs?: any) => { case "value": updateValueAttrib(el, val); break; - case "id": + case "accesskey": + (el).accessKey = val; + break; + case "contenteditable": + (el).contentEditable = val; + break; + case "tabindex": + (el).tabIndex = val; + break; + case "align": + case "autocapitalize": case "checked": + case "dir": + case "draggable": + case "hidden": + case "id": + case "lang": + case "namespaceURI": case "scrollTop": case "scrollLeft": - // TODO add more native attribs? + case "title": + // TODO add more properties / enumerated attribs? (el)[id] = val; break; default: - if (isListener) { - setListener(el, id.substr(2), val); - } else { - el.setAttribute(id, val); - } + isListener + ? setListener(el, id.substr(2), val) + : el.setAttribute(id, val === true ? "" : val); } } else { (el)[id] != null ? ((el)[id] = null) : el.removeAttribute(id); @@ -322,13 +320,10 @@ export const setListener = ( el: Element, id: string, listener: EventListener | [EventListener, boolean | AddEventListenerOptions] -) => { - if (isArray(listener)) { - el.addEventListener(id, ...listener); - } else { - el.addEventListener(id, listener); - } -}; +) => + isArray(listener) + ? el.addEventListener(id, ...listener) + : el.addEventListener(id, listener); /** * Removes event listener (possibly with options). @@ -341,13 +336,10 @@ export const removeListener = ( el: Element, id: string, listener: EventListener | [EventListener, boolean | AddEventListenerOptions] -) => { - if (isArray(listener)) { - el.removeEventListener(id, ...listener); - } else { - el.removeEventListener(id, listener); - } -}; +) => + isArray(listener) + ? el.removeEventListener(id, ...listener) + : el.removeEventListener(id, listener); export const clearDOM = (el: Element) => (el.innerHTML = ""); diff --git a/packages/hdom/src/normalize.ts b/packages/hdom/src/normalize.ts index 6e9e655135..59698d5635 100644 --- a/packages/hdom/src/normalize.ts +++ b/packages/hdom/src/normalize.ts @@ -1,6 +1,6 @@ import { isArray as isa, isNotStringAndIterable as isi, isPlainObject as iso } from "@thi.ng/checks"; import { illegalArgs } from "@thi.ng/errors"; -import { NO_SPANS, TAG_REGEXP } from "@thi.ng/hiccup"; +import { NO_SPANS, RE_TAG } from "@thi.ng/hiccup"; import { HDOMOpts } from "./api"; const isArray = isa; @@ -42,7 +42,7 @@ export const normalizeElement = (spec: any[], keys: boolean) => { let id: string; let clazz: string; let attribs; - if (typeof tag !== "string" || !(match = TAG_REGEXP.exec(tag))) { + if (typeof tag !== "string" || !(match = RE_TAG.exec(tag))) { illegalArgs(`${tag} is not a valid tag name`); } mtag = match![1]; @@ -65,7 +65,7 @@ export const normalizeElement = (spec: any[], keys: boolean) => { } } return attribs.__skip && spec.length < 3 - ? [mtag, attribs, ""] + ? [mtag, attribs] : [mtag, attribs, ...spec.slice(hasAttribs ? 2 : 1)]; }; diff --git a/packages/heaps/CHANGELOG.md b/packages/heaps/CHANGELOG.md index 5bf96be582..f2cd451603 100644 --- a/packages/heaps/CHANGELOG.md +++ b/packages/heaps/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@1.1.4...@thi.ng/heaps@1.1.5) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/heaps + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@1.1.3...@thi.ng/heaps@1.1.4) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/heaps + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@1.1.2...@thi.ng/heaps@1.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/heaps + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@1.1.1...@thi.ng/heaps@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/heaps diff --git a/packages/heaps/package.json b/packages/heaps/package.json index 87c9c0b4b4..d5585aa90b 100644 --- a/packages/heaps/package.json +++ b/packages/heaps/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/heaps", - "version": "1.1.2", + "version": "1.1.5", "description": "Generic binary heap & d-ary heap implementations with customizable ordering", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/compare": "^1.0.9" + "@thi.ng/api": "^6.5.0", + "@thi.ng/compare": "^1.0.10" }, "keywords": [ "data structure", diff --git a/packages/hiccup-carbon-icons/CHANGELOG.md b/packages/hiccup-carbon-icons/CHANGELOG.md index c9336d4ef2..bdd05b4a41 100644 --- a/packages/hiccup-carbon-icons/CHANGELOG.md +++ b/packages/hiccup-carbon-icons/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-carbon-icons@1.0.20...@thi.ng/hiccup-carbon-icons@1.0.21) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/hiccup-carbon-icons + + + + + +## [1.0.20](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-carbon-icons@1.0.19...@thi.ng/hiccup-carbon-icons@1.0.20) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/hiccup-carbon-icons + + + + + +## [1.0.19](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-carbon-icons@1.0.18...@thi.ng/hiccup-carbon-icons@1.0.19) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hiccup-carbon-icons + + + + + +## [1.0.18](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-carbon-icons@1.0.17...@thi.ng/hiccup-carbon-icons@1.0.18) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/hiccup-carbon-icons + + + + + ## [1.0.17](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-carbon-icons@1.0.16...@thi.ng/hiccup-carbon-icons@1.0.17) (2019-07-31) **Note:** Version bump only for package @thi.ng/hiccup-carbon-icons diff --git a/packages/hiccup-carbon-icons/package.json b/packages/hiccup-carbon-icons/package.json index db35607aa8..7ec91e1c43 100644 --- a/packages/hiccup-carbon-icons/package.json +++ b/packages/hiccup-carbon-icons/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-carbon-icons", - "version": "1.0.17", + "version": "1.0.21", "description": "Full set of IBM's Carbon icons in hiccup format", "module": "./index.js", "main": "./lib/index.js", @@ -25,13 +25,13 @@ "pub": "yarn build:release && yarn publish --access public" }, "devDependencies": { - "@thi.ng/hiccup": "^3.2.2", + "@thi.ng/hiccup": "^3.2.6", "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "keywords": [ "assets", diff --git a/packages/hiccup-css/CHANGELOG.md b/packages/hiccup-css/CHANGELOG.md index 679118b834..598b70769b 100644 --- a/packages/hiccup-css/CHANGELOG.md +++ b/packages/hiccup-css/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@1.1.5...@thi.ng/hiccup-css@1.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/hiccup-css + + + + + +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@1.1.4...@thi.ng/hiccup-css@1.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/hiccup-css + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@1.1.3...@thi.ng/hiccup-css@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hiccup-css + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@1.1.2...@thi.ng/hiccup-css@1.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/hiccup-css + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@1.1.1...@thi.ng/hiccup-css@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/hiccup-css diff --git a/packages/hiccup-css/package.json b/packages/hiccup-css/package.json index 835467ab69..46850d533b 100644 --- a/packages/hiccup-css/package.json +++ b/packages/hiccup-css/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-css", - "version": "1.1.2", + "version": "1.1.6", "description": "CSS from nested JS data structures", "module": "./index.js", "main": "./lib/index.js", @@ -29,14 +29,14 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "clojure", diff --git a/packages/hiccup-markdown/CHANGELOG.md b/packages/hiccup-markdown/CHANGELOG.md index e271642ec2..2615f4b385 100644 --- a/packages/hiccup-markdown/CHANGELOG.md +++ b/packages/hiccup-markdown/CHANGELOG.md @@ -3,6 +3,43 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-markdown@1.1.5...@thi.ng/hiccup-markdown@1.1.6) (2019-11-09) + + +### Bug Fixes + +* **hiccup-markdown:** [#156](https://github.com/thi-ng/umbrella/issues/156) fix blockquote default tag factory args, add test ([12e445a](https://github.com/thi-ng/umbrella/commit/12e445ac27960d3498d8b57ed6daa1520a60158e)) +* **hiccup-markdown:** [#156](https://github.com/thi-ng/umbrella/issues/156) undo trimming of element children ([ccc9d40](https://github.com/thi-ng/umbrella/commit/ccc9d40723df1f898fba70be2e15352b8dfcb909)) +* **hiccup-markdown:** [#156](https://github.com/thi-ng/umbrella/issues/156) update parse(), remove CR chars, add initial test ([602510c](https://github.com/thi-ng/umbrella/commit/602510c5150dbf26d43a1c9e7ca8afd7c5230f28)) + + + + + +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-markdown@1.1.4...@thi.ng/hiccup-markdown@1.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/hiccup-markdown + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-markdown@1.1.3...@thi.ng/hiccup-markdown@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/hiccup-markdown + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-markdown@1.1.2...@thi.ng/hiccup-markdown@1.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/hiccup-markdown + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-markdown@1.1.1...@thi.ng/hiccup-markdown@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/hiccup-markdown diff --git a/packages/hiccup-markdown/README.md b/packages/hiccup-markdown/README.md index dd5328cde5..21ff956979 100644 --- a/packages/hiccup-markdown/README.md +++ b/packages/hiccup-markdown/README.md @@ -14,11 +14,11 @@ This project is part of the - [Dependencies](#dependencies) - [Parser](#parser) - [Features](#features) - - [Limitations](#limitations) + - [Current issues & limitations](#current-issues--limitations) - [Other parser features](#other-parser-features) - [Serializing to HTML](#serializing-to-html) - [Customizing tags](#customizing-tags) -- [Serializer](#serializer) +- [Serializer (Hiccup to Markdown)](#serializer-hiccup-to-markdown) - [Features](#features-1) - [Behaviors](#behaviors) - [Usage examples](#usage-examples) @@ -31,7 +31,7 @@ This project is part of the This package provides both a customizable [Markdown](https://en.wikipedia.org/wiki/Markdown)-to-[Hiccup](https://github.com/thi-ng/umbrella/tree/master/packages/hiccup) -parser and an extensible Hiccup-to-Markdown serializer. +parser and an extensible Hiccup-to-Markdown converter. ## Installation @@ -41,12 +41,14 @@ yarn add @thi.ng/hiccup-markdown ## Dependencies +- [@thi.ng/arrays](https://github.com/thi-ng/umbrella/tree/master/packages/arrays) - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/master/packages/checks) - [@thi.ng/defmulti](https://github.com/thi-ng/umbrella/tree/master/packages/defmulti) - [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/master/packages/errors) - [@thi.ng/fsm](https://github.com/thi-ng/umbrella/tree/master/packages/fsm) - [@thi.ng/hiccup](https://github.com/thi-ng/umbrella/tree/master/packages/hiccup) - [@thi.ng/strings](https://github.com/thi-ng/umbrella/tree/master/packages/strings) +- [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) ## Parser @@ -72,11 +74,17 @@ features: | Code block | GFM only (triple backtick prefix), w/ optional language hint | | Horiz. Rule | only dash supported (e.g. `---`), min 3 chars required | -Note: Currently, the last heading, paragraph, blockquote, list or table requires an additional newline. +**Note: Because of MD's line break handling and the fact the parser only +consumes single characters from an iterable without knowledge of further +values, the last heading, paragraph, blockquote, list or table requires +an additional newline.** -### Limitations +### Current issues & limitations -These MD features (and probably many more) are **not** supported: +Paragraphs, headings and blockquotes ending with a character involved w/ inline formatting (e.g. `!`, `~`, `*`, `_`) either require an additional space or 2 empty lines (instead of just one) between the next paragraph. See [#156](https://github.com/thi-ng/umbrella/issues/156) for details. + +Also, these MD features (and probably many more) are currently **not** +supported: - inline HTML - nested inline formats (e.g. **bold** inside _italic_) @@ -89,10 +97,13 @@ These MD features (and probably many more) are **not** supported: Some of these are considered, though currently not high priority... Pull requests are welcome, though! + ### Other parser features - **Functional:** parser entirely built using [transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) + (specifically those defined in + [@thi.ng/fsm](https://github.com/thi-ng/umbrella/tree/master/packages/fsm)) & function composition. Use the parser in a transducer pipeline to easily apply post-processing of the emitted results - **Declarative:** parsing rules defined declaratively with only minimal @@ -104,8 +115,8 @@ requests are welcome, though! or the serializer of this package for back conversion to MD - **Customizable:** supports custom tag factory functions to override default behavior / representation of each parsed result element -- **Fast (enough):** parses this markdown file (5.8KB) in ~5ms on MBP2016 / Chrome 71 -- **Small:** minified + gzipped ~2.6KB (parser sub-module incl. deps) +- **Fast (enough):** parses this markdown file (5.9KB) in ~5ms on MBP2016 / Chrome 71 +- **Small:** minified + gzipped ~2.5KB (parser sub-module incl. deps) ### Serializing to HTML @@ -146,7 +157,7 @@ elements. User implementations / overrides can be given to the ```ts interface TagFactories { - blockquote(...children: any[]): any[]; + blockquote(children: any[]): any[]; code(body: string): any[]; codeblock(lang: string, body: string): any[]; em(body: string): any[]; @@ -178,7 +189,7 @@ serialize(iterator(parse(tags), src)); // ``` -## Serializer +## Serializer (Hiccup to Markdown) For the reverse operation, the `serialize()` function can be used to convert an hiccup component tree into Markdown. Currently supports most diff --git a/packages/hiccup-markdown/package.json b/packages/hiccup-markdown/package.json index 074d3c235d..1f77b3869d 100644 --- a/packages/hiccup-markdown/package.json +++ b/packages/hiccup-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-markdown", - "version": "1.1.2", + "version": "1.1.6", "description": "Markdown serialization of hiccup DOM trees", "module": "./index.js", "main": "./lib/index.js", @@ -29,18 +29,18 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/arrays": "^0.2.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/defmulti": "^1.1.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/fsm": "^2.2.2", - "@thi.ng/hiccup": "^3.2.2", - "@thi.ng/strings": "^1.2.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/arrays": "^0.3.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/defmulti": "^1.2.0", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/fsm": "^2.3.0", + "@thi.ng/hiccup": "^3.2.6", + "@thi.ng/strings": "^1.3.1", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "ES6", diff --git a/packages/hiccup-markdown/src/api.ts b/packages/hiccup-markdown/src/api.ts index 32e28e9004..f24d2d69ff 100644 --- a/packages/hiccup-markdown/src/api.ts +++ b/packages/hiccup-markdown/src/api.ts @@ -1,5 +1,5 @@ export interface TagFactories { - blockquote(...children: any[]): any[]; + blockquote(children: any[]): any[]; code(body: string): any[]; codeblock(lang: string, body: string): any[]; em(body: string): any[]; diff --git a/packages/hiccup-markdown/src/parse.ts b/packages/hiccup-markdown/src/parse.ts index 87e8ada3ab..82479f456a 100644 --- a/packages/hiccup-markdown/src/parse.ts +++ b/packages/hiccup-markdown/src/parse.ts @@ -11,6 +11,7 @@ import { untilStr, whitespace } from "@thi.ng/fsm"; +import { comp, filter } from "@thi.ng/transducers"; import { TagFactories } from "./api"; type ParseResult = ResultBody; @@ -58,7 +59,7 @@ interface FSMCtx { * Default hiccup element factories */ const DEFAULT_TAGS: TagFactories = { - blockquote: (...xs) => ["blockquote", ...xs], + blockquote: (xs) => ["blockquote", ...xs], code: (body) => ["code", body], codeblock: (lang, body) => ["pre", { lang }, body], em: (body) => ["em", body], @@ -243,122 +244,126 @@ const newTable = (ctx: FSMCtx) => ( */ export const parse = (_tags?: Partial) => { const tags = { ...DEFAULT_TAGS, ..._tags }; - return fsm( - { - [State.START]: alts( - [ - whitespace(() => [State.START]), - repeat(str(HD), 1, Infinity, heading), - str(BQUOTE, (ctx) => transition(ctx, State.BLOCKQUOTE)), - str(LI, newList), - alts( - [ - seq([str(CODE), not(str(CODE))], newParaCode), - str(CODEBLOCK, () => [State.START_CODEBLOCK]) - ], - undefined, - (_, next) => next - ), - seq([repeat(str(HR), 3, Infinity), str(NL)], () => [ - State.START, - [tags.hr()] - ]), - str(IMG, newParaInline(State.IMG)), - str(LINK_LABEL, newParaInline(State.LINK)), - str(STRONG, newParaInline(State.STRONG)), - str(STRIKE, newParaInline(State.STRIKE)), - str(EM, newParaInline(State.EMPHASIS)), - str(TD, newTable) - ], - newPara - ), - - [State.PARA]: matchPara(State.PARA, State.END_PARA), - - [State.END_PARA]: alts( - [ - ...matchInline(State.PARA), - str(NL, collectAndRestart(tags.paragraph)) - ], - collect(State.PARA) - ), - - [State.BLOCKQUOTE]: matchPara( - State.BLOCKQUOTE, - State.END_BLOCKQUOTE - ), - - [State.END_BLOCKQUOTE]: alts( - [ - ...matchInline(State.BLOCKQUOTE), - str(BQUOTE, collectBlockQuote), - str(NL, collectAndRestart(tags.blockquote)) - ], - collect(State.BLOCKQUOTE) - ), - - [State.HEADING]: matchPara(State.HEADING, State.END_HEADING), - - [State.END_HEADING]: alts( - [ - ...matchInline(State.HEADING), - str(NL, collectHeading(tags.heading)) - ], - collect(State.HEADING) - ), - - [State.START_CODEBLOCK]: untilStr( - NL, - (ctx, lang) => ((ctx.lang = lang), [State.CODEBLOCK]) - ), - - [State.CODEBLOCK]: untilStr( - CODEBLOCK_END, - collectCodeBlock(tags.codeblock) - ), - - [State.LI]: matchPara(State.LI, State.END_LI), - - [State.END_LI]: alts( - [ - str(NL, collectList("ul", tags.list, tags.li)), - str( - LI, - (ctx) => ( - collectLi(ctx, tags.li), transition(ctx, State.LI) + return comp( + filter((x) => x !== "\r"), + fsm( + { + [State.START]: alts( + [ + whitespace(() => [State.START]), + repeat(str(HD), 1, Infinity, heading), + str(BQUOTE, (ctx) => transition(ctx, State.BLOCKQUOTE)), + str(LI, newList), + alts( + [ + seq([str(CODE), not(str(CODE))], newParaCode), + str(CODEBLOCK, () => [State.START_CODEBLOCK]) + ], + undefined, + (_, next) => next + ), + seq([repeat(str(HR), 3, Infinity), str(NL)], () => [ + State.START, + [tags.hr()] + ]), + str(IMG, newParaInline(State.IMG)), + str(LINK_LABEL, newParaInline(State.LINK)), + str(STRONG, newParaInline(State.STRONG)), + str(STRIKE, newParaInline(State.STRIKE)), + str(EM, newParaInline(State.EMPHASIS)), + str(TD, newTable) + ], + newPara + ), + + [State.PARA]: matchPara(State.PARA, State.END_PARA), + + [State.END_PARA]: alts( + [ + ...matchInline(State.PARA), + str(NL, collectAndRestart(tags.paragraph)) + ], + collect(State.PARA) + ), + + [State.BLOCKQUOTE]: matchPara( + State.BLOCKQUOTE, + State.END_BLOCKQUOTE + ), + + [State.END_BLOCKQUOTE]: alts( + [ + ...matchInline(State.BLOCKQUOTE), + str(BQUOTE, collectBlockQuote), + str(NL, collectAndRestart(tags.blockquote)) + ], + collect(State.BLOCKQUOTE) + ), + + [State.HEADING]: matchPara(State.HEADING, State.END_HEADING), + + [State.END_HEADING]: alts( + [ + ...matchInline(State.HEADING), + str(NL, collectHeading(tags.heading)) + ], + collect(State.HEADING) + ), + + [State.START_CODEBLOCK]: untilStr( + NL, + (ctx, lang) => ((ctx.lang = lang), [State.CODEBLOCK]) + ), + + [State.CODEBLOCK]: untilStr( + CODEBLOCK_END, + collectCodeBlock(tags.codeblock) + ), + + [State.LI]: matchPara(State.LI, State.END_LI), + + [State.END_LI]: alts( + [ + str(NL, collectList("ul", tags.list, tags.li)), + str( + LI, + (ctx) => ( + collectLi(ctx, tags.li), + transition(ctx, State.LI) + ) ) - ) - ], - collect(State.LI) - ), + ], + collect(State.LI) + ), - [State.LINK]: matchLink(tags.link), + [State.LINK]: matchLink(tags.link), - [State.IMG]: matchLink(tags.img), + [State.IMG]: matchLink(tags.img), - [State.STRONG]: untilStr(STRONG, collectInline(tags.strong)), + [State.STRONG]: untilStr(STRONG, collectInline(tags.strong)), - [State.STRIKE]: untilStr(STRIKE, collectInline(tags.strike)), + [State.STRIKE]: untilStr(STRIKE, collectInline(tags.strike)), - [State.EMPHASIS]: untilStr(EM, collectInline(tags.em)), + [State.EMPHASIS]: untilStr(EM, collectInline(tags.em)), - [State.CODE]: untilStr(CODE, collectInline(tags.code)), + [State.CODE]: untilStr(CODE, collectInline(tags.code)), - [State.TABLE]: alts( - [ - ...matchInline(State.TABLE), - str(TD, collectTD(tags.td)), - str(NL, collectTR(tags.tr)) - ], - collect(State.TABLE) - ), + [State.TABLE]: alts( + [ + ...matchInline(State.TABLE), + str(TD, collectTD(tags.td)), + str(NL, collectTR(tags.tr)) + ], + collect(State.TABLE) + ), - [State.END_TABLE]: alts([ - str(NL, collectTable(tags.table)), - str(TD, () => [State.TABLE]) - ]) - }, - { stack: [] }, - State.START + [State.END_TABLE]: alts([ + str(NL, collectTable(tags.table)), + str(TD, () => [State.TABLE]) + ]) + }, + { stack: [] }, + State.START + ) ); }; diff --git a/packages/hiccup-markdown/src/serialize.ts b/packages/hiccup-markdown/src/serialize.ts index 3aa4a79d38..dc286ecf53 100644 --- a/packages/hiccup-markdown/src/serialize.ts +++ b/packages/hiccup-markdown/src/serialize.ts @@ -9,10 +9,17 @@ import { illegalArgs } from "@thi.ng/errors"; import { normalize } from "@thi.ng/hiccup"; import { repeat, wrap } from "@thi.ng/strings"; +interface SerializeState { + indent: number; + sep: string; + id?: number; + pre?: boolean; +} + export const serialize = (tree: any, ctx: any) => _serialize(tree, ctx, { indent: 0, sep: "" }); -const _serialize = (tree: any, ctx: any, state: any): string => { +const _serialize = (tree: any, ctx: any, state: SerializeState): string => { if (tree == null) return ""; if (Array.isArray(tree)) { if (!tree.length) { @@ -62,7 +69,11 @@ const _serialize = (tree: any, ctx: any, state: any): string => { return tree.toString(); }; -const serializeIter = (iter: Iterable, ctx: any, state: any) => { +const serializeIter = ( + iter: Iterable, + ctx: any, + state: SerializeState +) => { if (!iter) return ""; const res = []; for (let i of iter) { @@ -71,86 +82,79 @@ const serializeIter = (iter: Iterable, ctx: any, state: any) => { return res.join(state.sep); }; -const header = (level: number) => (el: any[], ctx: any, state: any) => - repeat("#", level) + " " + body(el, ctx, state) + "\n\n"; +const header = (level: number) => ( + el: any[], + ctx: any, + state: SerializeState +) => repeat("#", level) + " " + body(el, ctx, state) + "\n\n"; -const body = (el: any[], ctx: any, state: any) => +const body = (el: any[], ctx: any, state: SerializeState) => serializeIter(el[2], ctx, state); -export const serializeElement: MultiFn3 = defmulti( - (el) => el[0] -); +export const serializeElement: MultiFn3< + any, + any, + SerializeState, + string +> = defmulti((el) => el[0]); serializeElement.add(DEFAULT, body); -serializeElement.add("h1", header(1)); -serializeElement.add("h2", header(2)); -serializeElement.add("h3", header(3)); -serializeElement.add("h4", header(4)); -serializeElement.add("h5", header(5)); -serializeElement.add("h6", header(6)); -serializeElement.add("p", (el, ctx, state) => `\n${body(el, ctx, state)}\n`); +serializeElement.addAll({ + h1: header(1), + h2: header(2), + h3: header(3), + h4: header(4), + h5: header(5), + h6: header(6), + + p: (el, ctx, state) => `\n${body(el, ctx, state)}\n`, -serializeElement.add("img", (el) => `![${el[1].alt || ""}](${el[1].src})`); + img: (el) => `![${el[1].alt || ""}](${el[1].src})`, -serializeElement.add( - "a", - (el, ctx, state) => `[${body(el, ctx, state)}](${el[1].href})` -); + a: (el, ctx, state) => `[${body(el, ctx, state)}](${el[1].href})`, -serializeElement.add("em", (el, ctx, state) => `_${body(el, ctx, state)}_`); + em: (el, ctx, state) => `_${body(el, ctx, state)}_`, -serializeElement.add( - "strong", - (el, ctx, state) => `**${body(el, ctx, state)}**` -); + strong: (el, ctx, state) => `**${body(el, ctx, state)}**`, -serializeElement.add( - "pre", - (el, ctx, state) => + pre: (el, ctx, state) => `\n\`\`\`${el[1].lang || ""}\n${body(el, ctx, { ...state, pre: true, sep: "\n" - })}\n\`\`\`\n` -); - -serializeElement.add("code", (el, ctx, state) => - state.pre ? el[2][0] : `\`${body(el, ctx, state)}\`` -); - -serializeElement.add("ul", (el, ctx, state) => { - const cstate = { - ...state, - indent: state.indent + 4, - sep: "\n" - }; - return wrap(state.indent === 0 ? "\n" : "")(body(el, ctx, cstate)); -}); + })}\n\`\`\`\n`, -serializeElement.add("ol", (el, ctx, state) => { - const cstate = { - ...state, - indent: state.indent + 4, - id: 0, - sep: "\n" - }; - return wrap(state.indent === 0 ? "\n" : "")(body(el, ctx, cstate)); -}); + code: (el, ctx, state) => + state.pre ? el[2][0] : `\`${body(el, ctx, state)}\``, -serializeElement.add( - "li", - (el, ctx, state) => + ul: (el, ctx, state) => { + const cstate: SerializeState = { + ...state, + indent: state.indent + 4, + sep: "\n" + }; + return wrap(state.indent === 0 ? "\n" : "")(body(el, ctx, cstate)); + }, + + ol: (el, ctx, state) => { + const cstate: SerializeState = { + ...state, + indent: state.indent + 4, + id: 0, + sep: "\n" + }; + return wrap(state.indent === 0 ? "\n" : "")(body(el, ctx, cstate)); + }, + + li: (el, ctx, state) => repeat(" ", state.indent - 4) + (state.id != null ? ++state.id + "." : "-") + " " + - body(el, ctx, { ...state, sep: "" }) -); + body(el, ctx, { ...state, sep: "" }), -serializeElement.add( - "blockquote", - (el, ctx, state) => `\n> ${body(el, ctx, state)}\n` -); + blockquote: (el, ctx, state) => `\n> ${body(el, ctx, state)}\n`, -serializeElement.add("br", () => "\\\n"); + br: () => "\\\n", -serializeElement.add("hr", () => "\n---\n"); + hr: () => "\n---\n" +}); diff --git a/packages/hiccup-markdown/test/parse.ts b/packages/hiccup-markdown/test/parse.ts new file mode 100644 index 0000000000..9ac5fdf940 --- /dev/null +++ b/packages/hiccup-markdown/test/parse.ts @@ -0,0 +1,163 @@ +import { iterator } from "@thi.ng/transducers"; +import * as assert from "assert"; +import { parse } from "../src"; + +const check = (src: string, expected: any[]) => + assert.deepEqual([...iterator(parse(), src)], expected); + +describe("parse", () => { + it("CRLF", () => { + check(`# hello\r\n\r\nworld\r\n\r\n`, [ + ["h1", " hello "], + ["p", "world "] + ]); + }); + + it("blockquote", () => { + check(`>a block **quote** of\n> two _lines_.\n\n`, [ + [ + "blockquote", + "a block ", + ["strong", "quote"], + " of ", + ["br"], + " two ", + ["em", "lines"], + ". " + ] + ]); + }); + + it("code", () => { + check("inline `const example = 'indeed!'` code\n\n", [ + ["p", "inline ", ["code", "const example = 'indeed!'"], " code "] + ]); + }); + + it("code_block", () => { + check("```js\nconst code = () => 'indeed!'\n```\n", [ + ["pre", { lang: "js" }, "const code = () => 'indeed!'"] + ]); + }); + + it("em", () => { + check(`some _emphasized_ text\n\n`, [ + ["p", "some ", ["em", "emphasized"], " text "] + ]); + }); + + it("h1", () => { + check(`# Heading One\n\nbody\n\n`, [ + ["h1", " Heading One "], + ["p", "body "] + ]); + }); + + it("h2", () => { + check(`## Heading Two\n\nbody\n\n`, [ + ["h2", " Heading Two "], + ["p", "body "] + ]); + }); + + it("h3", () => { + check(`### Heading Three\n\nbody\n\n`, [ + ["h3", " Heading Three "], + ["p", "body "] + ]); + }); + + it("h4", () => { + check(`#### Heading Four\n\nbody\n\n`, [ + ["h4", " Heading Four "], + ["p", "body "] + ]); + }); + + it("h5", () => { + check(`##### Heading Five\n\nbody\n\n`, [ + ["h5", " Heading Five "], + ["p", "body "] + ]); + }); + + it("h6", () => { + check(`###### Heading Six\n\nbody\n\n`, [ + ["h6", " Heading Six "], + ["p", "body "] + ]); + }); + + it("h7", () => { + check(`####### Heading Seven\n\nbody\n\n`, [ + ["p", " Heading Seven "], + ["p", "body "] + ]); + }); + + it("hr", () => { + check(`---\n`, [["hr"]]); + }); + + it("img", () => { + check( + `![thi.ng](https://media.giphy.com/media/f6qMGmXuOdkwU/giphy.gif)\n\n`, + [ + [ + "p", + [ + "img", + { + src: + "https://media.giphy.com/media/f6qMGmXuOdkwU/giphy.gif", + alt: "thi.ng" + } + ], + " " + ] + ] + ); + }); + + it("li", () => { + check(`- an item\n- another\n\n`, [ + ["ul", ["li", "an item "], ["li", "another "]] + ]); + }); + + it("link", () => { + check(`come [to](http://thi.ng/umbrella) the light\n\n`, [ + [ + "p", + "come ", + ["a", { href: "http://thi.ng/umbrella" }, "to"], + " the light " + ] + ]); + }); + + it("strike", () => { + check(`I ~~am amazing~~ messed up\n\n`, [ + ["p", "I ", ["del", "am amazing"], " messed up "] + ]); + }); + + it("strong", () => { + check(`I **really** meant that\n\n`, [ + ["p", "I ", ["strong", "really"], " meant that "] + ]); + }); + + it("table", () => { + check(`| col1 | col2 |\n| --- | --- |\n| row1 | row2 |\n\n`, [ + [ + "table", + [ + "tbody", + ["tr", ["td", " col1 "], ["td", " col2 "]], + ["tr", ["td", " row1 "], ["td", " row2 "]] + ] + ] + ]); + }); +}); diff --git a/packages/hiccup-markdown/test/index.ts b/packages/hiccup-markdown/test/serialize.ts similarity index 100% rename from packages/hiccup-markdown/test/index.ts rename to packages/hiccup-markdown/test/serialize.ts diff --git a/packages/hiccup-svg/CHANGELOG.md b/packages/hiccup-svg/CHANGELOG.md index 30f413cdfa..fc47600df9 100644 --- a/packages/hiccup-svg/CHANGELOG.md +++ b/packages/hiccup-svg/CHANGELOG.md @@ -3,6 +3,65 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.3.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@3.3.1...@thi.ng/hiccup-svg@3.3.2) (2019-11-09) + + +### Bug Fixes + +* **hiccup-svg:** fix [#142](https://github.com/thi-ng/umbrella/issues/142), add missing exports (ellipse, image) ([1bd7f64](https://github.com/thi-ng/umbrella/commit/1bd7f6408e7b13f45363a8f90a9c043d27baffcb)) + + + + + +## [3.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@3.3.0...@thi.ng/hiccup-svg@3.3.1) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/hiccup-svg + + + + + +# [3.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@3.2.6...@thi.ng/hiccup-svg@3.3.0) (2019-08-21) + + +### Bug Fixes + +* **hiccup-svg:** convertAttrib() arg order ([8b48a27](https://github.com/thi-ng/umbrella/commit/8b48a27)) + + +### Features + +* **hiccup-svg:** update polyline(), add fill: none default ([cff9e30](https://github.com/thi-ng/umbrella/commit/cff9e30)) + + + + + +## [3.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@3.2.5...@thi.ng/hiccup-svg@3.2.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/hiccup-svg + + + + + +## [3.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@3.2.4...@thi.ng/hiccup-svg@3.2.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/hiccup-svg + + + + + +## [3.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@3.2.3...@thi.ng/hiccup-svg@3.2.4) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/hiccup-svg + + + + + ## [3.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@3.2.2...@thi.ng/hiccup-svg@3.2.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/hiccup-svg diff --git a/packages/hiccup-svg/package.json b/packages/hiccup-svg/package.json index d90bd3eecb..458302e84b 100644 --- a/packages/hiccup-svg/package.json +++ b/packages/hiccup-svg/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-svg", - "version": "3.2.3", + "version": "3.3.2", "description": "SVG element functions for @thi.ng/hiccup & @thi.ng/hdom", "module": "./index.js", "main": "./lib/index.js", @@ -29,13 +29,13 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/checks": "^2.2.2", - "@thi.ng/color": "^1.0.0", - "@thi.ng/hiccup": "^3.2.2" + "@thi.ng/checks": "^2.4.1", + "@thi.ng/color": "^1.1.2", + "@thi.ng/hiccup": "^3.2.6" }, "keywords": [ "components", diff --git a/packages/hiccup-svg/src/convert.ts b/packages/hiccup-svg/src/convert.ts index dc316dc20a..d8d09e3693 100644 --- a/packages/hiccup-svg/src/convert.ts +++ b/packages/hiccup-svg/src/convert.ts @@ -121,31 +121,36 @@ const convertAttribs = (attribs: any) => { // convertTransforms(res, attribs); for (let id in attribs) { const v = attribs[id]; - if (ATTRIB_ALIASES[id]) { - res[ATTRIB_ALIASES[id]] = v; + const aid = ATTRIB_ALIASES[id]; + if (aid) { + res[aid] = v; } else { - switch (id) { - case "font": { - const i = v.indexOf(" "); - res["font-size"] = v.substr(0, i); - res["font-family"] = v.substr(i + 1); - break; - } - case "align": - res["text-anchor"] = TEXT_ALIGN[v]; - break; - case "baseline": - // no SVG support? - break; - case "filter": - // TODO needs to be translated into def first - // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter - // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter - break; - default: - res[id] = v; - } + convertAttrib(res, id, v); } } return res; }; + +const convertAttrib = (res: any, id: string, v: any) => { + switch (id) { + case "font": { + const i = v.indexOf(" "); + res["font-size"] = v.substr(0, i); + res["font-family"] = v.substr(i + 1); + break; + } + case "align": + res["text-anchor"] = TEXT_ALIGN[v]; + break; + case "baseline": + // no SVG support? + break; + case "filter": + // TODO needs to be translated into def first + // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter + // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter + break; + default: + res[id] = v; + } +}; diff --git a/packages/hiccup-svg/src/format.ts b/packages/hiccup-svg/src/format.ts index 998132b7c0..a1f2c12a9c 100644 --- a/packages/hiccup-svg/src/format.ts +++ b/packages/hiccup-svg/src/format.ts @@ -1,5 +1,5 @@ -import { isArrayLike, isNumber, isString } from "@thi.ng/checks"; -import { asCSS, ColorMode, ReadonlyColor } from "@thi.ng/color"; +import { isArrayLike, isString } from "@thi.ng/checks"; +import { resolveAsCSS } from "@thi.ng/color"; import { Vec2Like } from "./api"; let PRECISION = 2; @@ -30,11 +30,11 @@ export const fpoints = (pts: Vec2Like[], sep = " ") => * therefore need to be complete, e.g. `{ rotate: "rotate(60)" }` * * For color related attribs (`fill`, `stroke`), if given value is - * array-like or a number, it will be converted into a CSS color string - * using thi.ng/color/asCSS. + * array-like, a number or an `IColor` instance, it will be converted + * into a CSS color string using thi.ng/color's `asCSS()`. * * String color attribs prefixed with `$` are replaced with `url(#...)` - * refs (used for gradients). + * refs (used for referencing gradients). * * Returns updated attribs or `undefined` if `attribs` itself is * null-ish. @@ -73,31 +73,36 @@ const ftransforms = (attribs: any) => { delete attribs.rotate; delete attribs.scale; } else { - const tx: string[] = []; - if ((v = attribs.translate)) { - tx.push(isString(v) ? v : `translate(${ff(v[0])} ${ff(v[1])})`); - delete attribs.translate; - } - if ((v = attribs.rotate)) { - tx.push(isString(v) ? v : `rotate(${ff((v * 180) / Math.PI)})`); - delete attribs.rotate; - } - if ((v = attribs.scale)) { - tx.push( - isString(v) - ? v - : isArrayLike(v) - ? `scale(${ff(v[0])} ${ff(v[1])})` - : `scale(${ff(v)})` - ); - delete attribs.scale; - } - attribs.transform = tx.join(" "); + attribs.transform = buildTransform(attribs); } } return attribs; }; +const buildTransform = (attribs: any) => { + const tx: string[] = []; + let v: any; + if ((v = attribs.translate)) { + tx.push(isString(v) ? v : `translate(${ff(v[0])} ${ff(v[1])})`); + delete attribs.translate; + } + if ((v = attribs.rotate)) { + tx.push(isString(v) ? v : `rotate(${ff((v * 180) / Math.PI)})`); + delete attribs.rotate; + } + if ((v = attribs.scale)) { + tx.push( + isString(v) + ? v + : isArrayLike(v) + ? `scale(${ff(v[0])} ${ff(v[1])})` + : `scale(${ff(v)})` + ); + delete attribs.scale; + } + return tx.join(" "); +}; + /** * Attempts to convert a single color attrib value. * @@ -110,10 +115,4 @@ export const fcolor = (col: any) => ? col[0] === "$" ? `url(#${col.substr(1)})` : col - : isArrayLike(col) - ? isNumber((col).mode) - ? asCSS(col) - : asCSS(col, ColorMode.RGBA) - : isNumber(col) - ? asCSS(col, ColorMode.INT32) - : col; + : resolveAsCSS(col); diff --git a/packages/hiccup-svg/src/index.ts b/packages/hiccup-svg/src/index.ts index f9fcba1ca4..17b4396920 100644 --- a/packages/hiccup-svg/src/index.ts +++ b/packages/hiccup-svg/src/index.ts @@ -1,8 +1,10 @@ export * from "./api"; export * from "./circle"; export * from "./defs"; +export * from "./ellipse"; export * from "./gradients"; export * from "./group"; +export * from "./image"; export * from "./line"; export * from "./path"; export * from "./points"; diff --git a/packages/hiccup-svg/src/points.ts b/packages/hiccup-svg/src/points.ts index 318185a7bc..3adda59c44 100644 --- a/packages/hiccup-svg/src/points.ts +++ b/packages/hiccup-svg/src/points.ts @@ -21,18 +21,8 @@ export const points = ( const group = ["g", fattribs({ ...attribs })]; let href: string; if (!shape || shape[0] !== "#") { - const r = ff(size); href = "_" + ((Math.random() * 1e6) | 0).toString(36); - group.push([ - "g", - { opacity: 0 }, - shape === "circle" - ? ["circle", { id: href, cx: 0, cy: 0, r: r }] - : [ - "rect", - { id: href, x: -r / 2, y: -r / 2, width: r, height: r } - ] - ]); + group.push(["g", { opacity: 0 }, buildShape(shape, href, size)]); href = "#" + href; } else { href = shape; @@ -43,3 +33,12 @@ export const points = ( } return group; }; + +const buildShape = (shape: string, id: string, r: number) => { + const rf = ff(r); + if (shape === "circle") { + return ["circle", { id, cx: 0, cy: 0, r: rf }]; + } + const rf2 = ff(-r / 2); + return ["rect", { id, x: rf2, y: rf2, width: rf, height: rf }]; +}; diff --git a/packages/hiccup-svg/src/polyline.ts b/packages/hiccup-svg/src/polyline.ts index 2dcf187b9b..76b5932519 100644 --- a/packages/hiccup-svg/src/polyline.ts +++ b/packages/hiccup-svg/src/polyline.ts @@ -4,7 +4,8 @@ import { fattribs, fpoints } from "./format"; export const polyline = (pts: Vec2Like[], attribs?: any): any[] => [ "polyline", fattribs({ - ...attribs, - points: fpoints(pts) + fill: "none", + points: fpoints(pts), + ...attribs }) ]; diff --git a/packages/hiccup/CHANGELOG.md b/packages/hiccup/CHANGELOG.md index a18d217704..1591438560 100644 --- a/packages/hiccup/CHANGELOG.md +++ b/packages/hiccup/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@3.2.5...@thi.ng/hiccup@3.2.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/hiccup + + + + + +## [3.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@3.2.4...@thi.ng/hiccup@3.2.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/hiccup + + + + + +## [3.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@3.2.3...@thi.ng/hiccup@3.2.4) (2019-08-21) + + +### Bug Fixes + +* **hiccup:** update/rename regexes & tag maps ([6dba80d](https://github.com/thi-ng/umbrella/commit/6dba80d)) + + + + + +## [3.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@3.2.2...@thi.ng/hiccup@3.2.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/hiccup + + + + + ## [3.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@3.2.1...@thi.ng/hiccup@3.2.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/hiccup diff --git a/packages/hiccup/package.json b/packages/hiccup/package.json index 06eef71a1d..f1d28a32fd 100644 --- a/packages/hiccup/package.json +++ b/packages/hiccup/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup", - "version": "3.2.2", + "version": "3.2.6", "description": "HTML/SVG/XML serialization of nested data structures, iterables & closures", "module": "./index.js", "main": "./lib/index.js", @@ -25,17 +25,17 @@ "pub": "yarn build:release && yarn publish --access public" }, "devDependencies": { - "@thi.ng/atom": "^3.0.2", + "@thi.ng/atom": "^3.1.1", "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/checks": "^2.2.2", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/checks": "^2.4.1", + "@thi.ng/errors": "^1.2.1" }, "keywords": [ "clojure", diff --git a/packages/hiccup/src/api.ts b/packages/hiccup/src/api.ts index 0a5d533d30..e4fb67bc3f 100644 --- a/packages/hiccup/src/api.ts +++ b/packages/hiccup/src/api.ts @@ -2,8 +2,6 @@ export const SVG_NS = "http://www.w3.org/2000/svg"; export const XLINK_NS = "http://www.w3.org/1999/xlink"; export const XHTML_NS = "http://www.w3.org/1999/xhtml"; -export const TAG_REGEXP = /^([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?$/; - export const PROC_TAGS: { [id: string]: string } = { "?xml": "?>\n", "!DOCTYPE": ">\n", @@ -12,20 +10,6 @@ export const PROC_TAGS: { [id: string]: string } = { "!ATTLIST": ">\n" }; -// tslint:disable-next-line -export const SVG_TAGS: { - [id: string]: number; -} = "animate animateColor animateMotion animateTransform circle clipPath color-profile defs desc discard ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter font foreignObject g image line linearGradient marker mask metadata mpath path pattern polygon polyline radialGradient rect set stop style svg switch symbol text textPath title tref tspan use view" - .split(" ") - .reduce((acc: any, x) => ((acc[x] = 1), acc), {}); - -// tslint:disable-next-line -export const VOID_TAGS: { - [id: string]: number; -} = "area base br circle col command ellipse embed hr img input keygen line link meta param path polygon polyline rect source stop track use wbr ?xml" - .split(" ") - .reduce((acc: any, x) => ((acc[x] = 1), acc), {}); - export const ENTITIES: { [id: string]: string } = { "&": "&", "<": "<", @@ -34,6 +18,9 @@ export const ENTITIES: { [id: string]: string } = { "'": "'" }; +export const RE_TAG = /^([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?$/; +export const RE_ENTITY = new RegExp(`[${Object.keys(ENTITIES).join("")}]`, "g"); + export const COMMENT = "__COMMENT__"; export const NO_SPANS: { @@ -45,4 +32,18 @@ export const NO_SPANS: { textarea: 1 }; -export const ENTITY_RE = new RegExp(`[${Object.keys(ENTITIES)}]`, "g"); +const tagMap = ( + tags: string +): { + [id: string]: boolean; +} => tags.split(" ").reduce((acc: any, x) => ((acc[x] = true), acc), {}); + +// tslint:disable-next-line +export const SVG_TAGS = tagMap( + "animate animateColor animateMotion animateTransform circle clipPath color-profile defs desc discard ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter font foreignObject g image line linearGradient marker mask metadata mpath path pattern polygon polyline radialGradient rect set stop style svg switch symbol text textPath title tref tspan use view" +); + +// tslint:disable-next-line +export const VOID_TAGS = tagMap( + "area base br circle col command ellipse embed hr img input keygen line link meta param path polygon polyline rect source stop track use wbr ?xml" +); diff --git a/packages/hiccup/src/escape.ts b/packages/hiccup/src/escape.ts index 954527dbf2..41a6784e28 100644 --- a/packages/hiccup/src/escape.ts +++ b/packages/hiccup/src/escape.ts @@ -1,3 +1,3 @@ -import { ENTITIES, ENTITY_RE } from "./api"; +import { ENTITIES, RE_ENTITY } from "./api"; -export const escape = (x: string) => x.replace(ENTITY_RE, (y) => ENTITIES[y]); +export const escape = (x: string) => x.replace(RE_ENTITY, (y) => ENTITIES[y]); diff --git a/packages/hiccup/src/index.ts b/packages/hiccup/src/index.ts index 15898b3dd8..834c6705ee 100644 --- a/packages/hiccup/src/index.ts +++ b/packages/hiccup/src/index.ts @@ -2,4 +2,5 @@ export * from "./api"; export * from "./css"; export * from "./deref"; export * from "./escape"; +export * from "./normalize"; export * from "./serialize"; diff --git a/packages/hiccup/src/normalize.ts b/packages/hiccup/src/normalize.ts new file mode 100644 index 0000000000..b0d3d03b17 --- /dev/null +++ b/packages/hiccup/src/normalize.ts @@ -0,0 +1,40 @@ +import { isPlainObject, isString } from "@thi.ng/checks"; +import { illegalArgs } from "@thi.ng/errors"; +import { RE_TAG } from "./api"; +import { css } from "./css"; + +export const normalize = (tag: any[]) => { + let el = tag[0]; + let match: RegExpExecArray | null; + let id: string; + let clazz: string; + const hasAttribs = isPlainObject(tag[1]); + const attribs: any = hasAttribs ? { ...tag[1] } : {}; + if (!isString(el) || !(match = RE_TAG.exec(el))) { + illegalArgs(`"${el}" is not a valid tag name`); + } + el = match![1]; + id = match![2]; + clazz = match![3]; + if (id) { + attribs.id = id; + } + if (clazz) { + clazz = clazz.replace(/\./g, " "); + if (attribs.class) { + attribs.class += " " + clazz; + } else { + attribs.class = clazz; + } + } + if (tag.length > 1) { + if (isPlainObject(attribs.style)) { + attribs.style = css(attribs.style); + } + tag = tag.slice(hasAttribs ? 2 : 1).filter((x) => x != null); + if (tag.length > 0) { + return [el, attribs, tag]; + } + } + return [el, attribs]; +}; diff --git a/packages/hiccup/src/serialize.ts b/packages/hiccup/src/serialize.ts index bd5af353df..a2ce486203 100644 --- a/packages/hiccup/src/serialize.ts +++ b/packages/hiccup/src/serialize.ts @@ -2,7 +2,6 @@ import { implementsFunction, isFunction, isNotStringAndIterable, - isPlainObject, isString } from "@thi.ng/checks"; import { illegalArgs } from "@thi.ng/errors"; @@ -10,11 +9,10 @@ import { COMMENT, NO_SPANS, PROC_TAGS, - TAG_REGEXP, VOID_TAGS } from "./api"; -import { css } from "./css"; import { escape } from "./escape"; +import { normalize } from "./normalize"; /** * Recursively normalizes and serializes given tree as HTML/SVG/XML @@ -164,94 +162,7 @@ const _serialize = ( return ""; } if (Array.isArray(tree)) { - if (!tree.length) { - return ""; - } - let tag = tree[0]; - if (isFunction(tag)) { - return _serialize( - tag.apply(null, [ctx, ...tree.slice(1)]), - ctx, - esc, - span, - keys, - path - ); - } - if (implementsFunction(tag, "render")) { - return _serialize( - tag.render.apply(null, [ctx, ...tree.slice(1)]), - ctx, - esc, - span, - keys, - path - ); - } - if (isString(tag)) { - if (tag === COMMENT) { - return tree.length > 2 - ? `\n\n` - : `\n\n`; - } - tree = normalize(tree); - tag = tree[0]; - const attribs = tree[1]; - if (attribs.__skip || attribs.__serialize === false) { - return ""; - } - let body = tree[2]; - let res = `<${tag}`; - if (keys && attribs.key === undefined) { - attribs.key = path.join("-"); - } - for (let a in attribs) { - if (a.startsWith("__")) continue; - if (attribs.hasOwnProperty(a)) { - let v = attribs[a]; - if (v != null) { - if (isFunction(v)) { - if (/^on\w+/.test(a) || (v = v(attribs)) == null) { - continue; - } - } - if (v === true) { - res += " " + a; - } else if (v !== false) { - v = v.toString(); - if (v.length) { - res += ` ${a}="${esc ? escape(v) : v}"`; - } - } - } - } - } - if (body) { - if (VOID_TAGS[tag]) { - illegalArgs(`No body allowed in tag: ${tag}`); - } - const proc = PROC_TAGS[tag]; - res += proc ? " " : ">"; - span = span && !proc && !NO_SPANS[tag]; - for (let i = 0, n = body.length; i < n; i++) { - res += _serialize(body[i], ctx, esc, span, keys, [ - ...path, - i - ]); - } - return (res += proc || ``); - } else if (!VOID_TAGS[tag]) { - return (res += `>`); - } - return (res += PROC_TAGS[tag] || "/>"); - } - if (isNotStringAndIterable(tree)) { - return _serializeIter(tree, ctx, esc, span, keys, path); - } - illegalArgs(`invalid tree node: ${tree}`); + return serializeElement(tree, ctx, esc, span, keys, path); } if (isFunction(tree)) { return _serialize(tree(ctx), ctx, esc, span, keys, path); @@ -263,7 +174,7 @@ const _serialize = ( return _serialize(tree.deref(), ctx, esc, span, keys, path); } if (isNotStringAndIterable(tree)) { - return _serializeIter(tree, ctx, esc, span, keys, path); + return serializeIter(tree, ctx, esc, span, keys, path); } tree = esc ? escape(tree.toString()) : tree; return span @@ -271,7 +182,113 @@ const _serialize = ( : tree; }; -const _serializeIter = ( +const serializeElement = ( + tree: any[], + ctx: any, + esc: boolean, + span: boolean, + keys: boolean, + path: any[] +) => { + if (!tree.length) { + return ""; + } + let tag = tree[0]; + if (isFunction(tag)) { + return _serialize( + tag.apply(null, [ctx, ...tree.slice(1)]), + ctx, + esc, + span, + keys, + path + ); + } + if (implementsFunction(tag, "render")) { + return _serialize( + tag.render.apply(null, [ctx, ...tree.slice(1)]), + ctx, + esc, + span, + keys, + path + ); + } + if (tag === COMMENT) { + return serializeComment(tree); + } + if (isString(tag)) { + tree = normalize(tree); + tag = tree[0]; + const attribs = tree[1]; + if (attribs.__skip || attribs.__serialize === false) { + return ""; + } + let body = tree[2]; + let res = `<${tag}`; + keys && attribs.key === undefined && (attribs.key = path.join("-")); + res += serializeAttribs(attribs, esc); + res += body + ? serializeBody(tag, body, ctx, esc, span, keys, path) + : !VOID_TAGS[tag] + ? `>` + : PROC_TAGS[tag] || "/>"; + return res; + } + if (isNotStringAndIterable(tree)) { + return serializeIter(tree, ctx, esc, span, keys, path); + } + return illegalArgs(`invalid tree node: ${tree}`); +}; + +const serializeAttribs = (attribs: any, esc: boolean) => { + let res = ""; + for (let a in attribs) { + if (a.startsWith("__")) continue; + let v = attribs[a]; + if (v == null) continue; + if (isFunction(v) && (/^on\w+/.test(a) || (v = v(attribs)) == null)) + continue; + if (v === true) { + res += " " + a; + } else if (v !== false) { + v = v.toString(); + v.length && (res += ` ${a}="${esc ? escape(v) : v}"`); + } + } + return res; +}; + +const serializeBody = ( + tag: string, + body: any[], + ctx: any, + esc: boolean, + span: boolean, + keys: boolean, + path: any[] +) => { + if (VOID_TAGS[tag]) { + illegalArgs(`No body allowed in tag: ${tag}`); + } + const proc = PROC_TAGS[tag]; + let res = proc ? " " : ">"; + span = span && !proc && !NO_SPANS[tag]; + for (let i = 0, n = body.length; i < n; i++) { + res += _serialize(body[i], ctx, esc, span, keys, [...path, i]); + } + return res + (proc || ``); +}; + +const serializeComment = (tree: any[]) => + tree.length > 2 + ? `\n\n` + : `\n\n`; + +const serializeIter = ( iter: Iterable, ctx: any, esc: boolean, @@ -287,39 +304,3 @@ const _serializeIter = ( } return res.join(""); }; - -export const normalize = (tag: any[]) => { - let el = tag[0]; - let match: RegExpExecArray | null; - let id: string; - let clazz: string; - const hasAttribs = isPlainObject(tag[1]); - const attribs: any = hasAttribs ? { ...tag[1] } : {}; - if (!isString(el) || !(match = TAG_REGEXP.exec(el))) { - illegalArgs(`"${el}" is not a valid tag name`); - } - el = match![1]; - id = match![2]; - clazz = match![3]; - if (id) { - attribs.id = id; - } - if (clazz) { - clazz = clazz.replace(/\./g, " "); - if (attribs.class) { - attribs.class += " " + clazz; - } else { - attribs.class = clazz; - } - } - if (tag.length > 1) { - if (isPlainObject(attribs.style)) { - attribs.style = css(attribs.style); - } - tag = tag.slice(hasAttribs ? 2 : 1).filter((x) => x != null); - if (tag.length > 0) { - return [el, attribs, tag]; - } - } - return [el, attribs]; -}; diff --git a/packages/iges/CHANGELOG.md b/packages/iges/CHANGELOG.md index 33e085e14a..9fb88e5016 100644 --- a/packages/iges/CHANGELOG.md +++ b/packages/iges/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/iges@1.1.10...@thi.ng/iges@1.1.11) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/iges + + + + + +## [1.1.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/iges@1.1.9...@thi.ng/iges@1.1.10) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/iges + + + + + +## [1.1.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/iges@1.1.8...@thi.ng/iges@1.1.9) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/iges + + + + + +## [1.1.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/iges@1.1.7...@thi.ng/iges@1.1.8) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/iges + + + + + ## [1.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/iges@1.1.6...@thi.ng/iges@1.1.7) (2019-07-31) **Note:** Version bump only for package @thi.ng/iges diff --git a/packages/iges/README.md b/packages/iges/README.md index f02f7baf14..4581179cbc 100644 --- a/packages/iges/README.md +++ b/packages/iges/README.md @@ -13,7 +13,7 @@ Bare-bones IGES 5.3 serializer for (currently only) polygonal geometry, both open & closed, for use in various CAD applications (e.g. Rhino, Houdini, Fusion 360) -![houdini](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/iges.png) +![houdini](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/iges/iges-houdini.png) ## Installation @@ -45,7 +45,7 @@ doc.start = [ "Defines single open 2D polyline (type 106)" ]; -iges.addPolyline2d(doc, [ +iges.addPolyline(doc, [ [0,0], [0, 100], [50, 150], @@ -60,6 +60,8 @@ iges.addPolyline2d(doc, [ console.log(iges.serialize(doc)); ``` +Resulting output (IGES is a text file format): + ```iges Example file for @thi.ng/iges S 1 Defines single open 2D polyline (type 106) S 2 diff --git a/packages/iges/package.json b/packages/iges/package.json index 23cf141adb..3a5890098b 100644 --- a/packages/iges/package.json +++ b/packages/iges/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/iges", - "version": "1.1.7", + "version": "1.1.11", "description": "IGES 5.3 serializer for (currently only) polygonal geometry, both open & closed", "module": "./index.js", "main": "./lib/index.js", @@ -29,15 +29,16 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/defmulti": "^1.1.2", - "@thi.ng/strings": "^1.2.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/defmulti": "^1.2.0", + "@thi.ng/strings": "^1.3.1", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "CAD", diff --git a/packages/iges/src/index.ts b/packages/iges/src/index.ts index 0c30a13eff..6c07c4ebfc 100644 --- a/packages/iges/src/index.ts +++ b/packages/iges/src/index.ts @@ -15,8 +15,9 @@ import { push, transduce, wordWrap, - wrap + wrapSides } from "@thi.ng/transducers"; +import { ReadonlyVec } from "@thi.ng/vectors"; import { BooleanNode, BooleanTree, @@ -254,7 +255,7 @@ const addEntity = ( export const addPolyline = ( doc: IGESDocument, - pts: ArrayLike[], + pts: ReadonlyVec[], form = PolylineMode.OPEN, opts?: Partial ) => { @@ -272,11 +273,9 @@ export const addPolyline = ( }, [ ...params, - ...mapcat( + ...mapcat( (p) => map((x) => [x, Type.FLOAT], p), - form === PolylineMode.CLOSED - ? wrap(pts, 1, false, true) - : pts + form === PolylineMode.CLOSED ? wrapSides(pts, 0, 1) : pts ) ], opts @@ -285,13 +284,13 @@ export const addPolyline = ( export const addPolygon = ( doc: IGESDocument, - pts: ArrayLike[], + pts: ReadonlyVec[], opts?: Partial ) => addPolyline(doc, pts, PolylineMode.FILLED, opts); export const addPoint = ( doc: IGESDocument, - p: ArrayLike, + p: ReadonlyVec, opts?: Partial ) => addEntity( @@ -309,8 +308,8 @@ export const addPoint = ( export const addLine = ( doc: IGESDocument, - a: ArrayLike, - b: ArrayLike, + a: ReadonlyVec, + b: ReadonlyVec, opts?: Partial ) => addEntity( @@ -356,10 +355,10 @@ const postOrder = (acc: Param[], tree: BooleanNode) => { export const addCSGBox = ( doc: IGESDocument, - pos: ArrayLike, - size: ArrayLike, - xaxis: ArrayLike = [1, 0, 0], - zaxis: ArrayLike = [0, 0, 1], + pos: ReadonlyVec, + size: ReadonlyVec, + xaxis: ReadonlyVec = [1, 0, 0], + zaxis: ReadonlyVec = [0, 0, 1], opts?: Partial ) => addEntity( @@ -385,10 +384,10 @@ export const addCSGBox = ( export const addCSGCylinder = ( doc: IGESDocument, - pos: ArrayLike, - normal: ArrayLike, - radius: ArrayLike, - height: ArrayLike, + pos: ReadonlyVec, + normal: ReadonlyVec, + radius: ReadonlyVec, + height: ReadonlyVec, opts?: Partial ) => addEntity( diff --git a/packages/imgui/.npmignore b/packages/imgui/.npmignore new file mode 100644 index 0000000000..24f388daa6 --- /dev/null +++ b/packages/imgui/.npmignore @@ -0,0 +1,18 @@ +.cache +.meta +.nyc_output +*.gz +*.html +*.svg +*.tgz +*.h +*.o +*.wasm +build +coverage +dev +doc +export +src* +test +tsconfig.json diff --git a/packages/imgui/CHANGELOG.md b/packages/imgui/CHANGELOG.md new file mode 100644 index 0000000000..83fe1b399f --- /dev/null +++ b/packages/imgui/CHANGELOG.md @@ -0,0 +1,93 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/imgui@0.1.3...@thi.ng/imgui@0.1.4) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/imgui + + + + + +## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/imgui@0.1.2...@thi.ng/imgui@0.1.3) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/imgui + + + + + +## [0.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/imgui@0.1.1...@thi.ng/imgui@0.1.2) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/imgui + + + + + +## [0.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/imgui@0.1.0...@thi.ng/imgui@0.1.1) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/imgui + + + + + +# 0.1.0 (2019-08-16) + + +### Bug Fixes + +* **imgui:** touch event handling (FF/Safari) ([af697d9](https://github.com/thi-ng/umbrella/commit/af697d9)) + + +### Features + +* **imgui:** add buttonV, radialMenu, update dropdown ([03d5932](https://github.com/thi-ng/umbrella/commit/03d5932)) +* **imgui:** add color type, keys, update default theme ([e4facae](https://github.com/thi-ng/umbrella/commit/e4facae)) +* **imgui:** add component resource caching & GC, update all comps & theme ([7c3d399](https://github.com/thi-ng/umbrella/commit/7c3d399)) +* **imgui:** add cursor & LayoutBox support, add docs ([b8d0892](https://github.com/thi-ng/umbrella/commit/b8d0892)) +* **imgui:** add cursor blink config, update textFieldRaw() ([1d80e14](https://github.com/thi-ng/umbrella/commit/1d80e14)) +* **imgui:** add dial widget, extract key handlers, update layout ([d3d2b27](https://github.com/thi-ng/umbrella/commit/d3d2b27)) +* **imgui:** add dialGroup, ringGroup, fix/update label hashing ([0333fa6](https://github.com/thi-ng/umbrella/commit/0333fa6)) +* **imgui:** add disabled component stack, update theme & behaviors ([dce481a](https://github.com/thi-ng/umbrella/commit/dce481a)) +* **imgui:** add dropdown widget, update hover behaviors ([b9d725a](https://github.com/thi-ng/umbrella/commit/b9d725a)) +* **imgui:** add GridLayout, update all components ([4f94981](https://github.com/thi-ng/umbrella/commit/4f94981)) +* **imgui:** add GridLayout.spansForSize/colsForWidth/rowsForHeight ([713dce1](https://github.com/thi-ng/umbrella/commit/713dce1)) +* **imgui:** add home/end key support in textField ([ae75c08](https://github.com/thi-ng/umbrella/commit/ae75c08)) +* **imgui:** add iconButton() ([07599a4](https://github.com/thi-ng/umbrella/commit/07599a4)) +* **imgui:** add IMGUI.clear(), update deps ([d10732d](https://github.com/thi-ng/umbrella/commit/d10732d)) +* **imgui:** add IMGUI.draw flag, update components, add/update hash fns ([c9bc287](https://github.com/thi-ng/umbrella/commit/c9bc287)) +* **imgui:** add key consts, update key handling (shift/alt mods) ([7809734](https://github.com/thi-ng/umbrella/commit/7809734)) +* **imgui:** add key handling for radialMenu() ([99c2987](https://github.com/thi-ng/umbrella/commit/99c2987)) +* **imgui:** add layouted sliderV/Group, add/update various comp ([7e0bfeb](https://github.com/thi-ng/umbrella/commit/7e0bfeb)) +* **imgui:** add slider value format, minor other updates ([399fa21](https://github.com/thi-ng/umbrella/commit/399fa21)) +* **imgui:** add textfield scrolling, cursor movement, word jump ([c94d4d9](https://github.com/thi-ng/umbrella/commit/c94d4d9)) +* **imgui:** add textField widget, update theme & key handling ([53b068f](https://github.com/thi-ng/umbrella/commit/53b068f)) +* **imgui:** add textTransformH/V, update buttons to allow any body ([05cc31f](https://github.com/thi-ng/umbrella/commit/05cc31f)) +* **imgui:** add theme stack, extract default event handlers ([b4aee22](https://github.com/thi-ng/umbrella/commit/b4aee22)) +* **imgui:** add toggle & radio buttons ([6a491aa](https://github.com/thi-ng/umbrella/commit/6a491aa)) +* **imgui:** add touch support, minor widget refactoring ([dcd19bc](https://github.com/thi-ng/umbrella/commit/dcd19bc)) +* **imgui:** add vertical slider, rename slider/sliderGroup ([40c050e](https://github.com/thi-ng/umbrella/commit/40c050e)) +* **imgui:** add XY-pad widget ([6446e6e](https://github.com/thi-ng/umbrella/commit/6446e6e)) +* **imgui:** add xyPad label offset args, minor refactoring ([d224fe0](https://github.com/thi-ng/umbrella/commit/d224fe0)) +* **imgui:** add/update layout types, handling, add more ctrl key consts ([4086590](https://github.com/thi-ng/umbrella/commit/4086590)) +* **imgui:** import as new package [@thi](https://github.com/thi).ng/imgui ([f94b430](https://github.com/thi-ng/umbrella/commit/f94b430)) +* **imgui:** non-destructive value updates, local state ([b499c8c](https://github.com/thi-ng/umbrella/commit/b499c8c)) +* **imgui:** rename dial => ring, add new dial, extract dialVal() ([cd9a339](https://github.com/thi-ng/umbrella/commit/cd9a339)) +* **imgui:** update button, dropdown, radio, sliderHGroup ([588a321](https://github.com/thi-ng/umbrella/commit/588a321)) +* **imgui:** update dropdown key handlers (Esc) ([c2ef036](https://github.com/thi-ng/umbrella/commit/c2ef036)) +* **imgui:** update dropdown, add tooltip support & tri icon ([d662811](https://github.com/thi-ng/umbrella/commit/d662811)) +* **imgui:** update IGridLayout & GridLayout.next() ([0c1d483](https://github.com/thi-ng/umbrella/commit/0c1d483)) +* **imgui:** update IMGUIOpts, input handling, optional event handling ([d06a235](https://github.com/thi-ng/umbrella/commit/d06a235)) +* **imgui:** update tab handling, allow all items unfocused ([1a63694](https://github.com/thi-ng/umbrella/commit/1a63694)) +* **imgui:** update textField, set cursor via mouse, update alt move/del ([4f9760d](https://github.com/thi-ng/umbrella/commit/4f9760d)) +* **imgui:** update theme init/config, add setTheme() ([76ad91c](https://github.com/thi-ng/umbrella/commit/76ad91c)) +* **imgui:** update toggleRaw() to update value earlier ([21ba39d](https://github.com/thi-ng/umbrella/commit/21ba39d)) + + +### Performance Improvements + +* **imgui:** update comp hashing to use murmur hash vs toString, use ES6 Maps ([7db92b9](https://github.com/thi-ng/umbrella/commit/7db92b9)) diff --git a/packages/imgui/LICENSE b/packages/imgui/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/imgui/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/imgui/README.md b/packages/imgui/README.md new file mode 100644 index 0000000000..22914f3966 --- /dev/null +++ b/packages/imgui/README.md @@ -0,0 +1,254 @@ +# @thi.ng/imgui + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/imgui.svg)](https://www.npmjs.com/package/@thi.ng/imgui) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/imgui.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + +- [About](#about) + - [Current features](#current-features) + - [Available components / widgets](#available-components--widgets) + - [State handling](#state-handling) + - [Layout support](#layout-support) + - [Key controls](#key-controls) + - [Current limitations](#current-limitations) + - [Status](#status) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) +- [Authors](#authors) +- [License](#license) + + + +## About + +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/imgui/imgui-all.png) + +Currently still somewhat bare-bones, but already usable & customizable [immediate +mode GUI](https://github.com/ocornut/imgui#references) implementation, +primarily for +[@thi.ng/hdom-canvas](https://github.com/thi-ng/umbrella/tree/master/packages/hdom-canvas) +and +[@thi.ng/webgl](https://github.com/thi-ng/umbrella/tree/master/packages/webgl), +however with no direct dependency on either and only outputting data structures. + +IMGUI components are largely ephemeral and expressed as simple +functions, producing a visual representation of a user state value. +IMGUIs are reconstructed from scratch each frame and don't exist +otherwise (apart from some rudimentary input state & config). If a +component's function isn't called again, it won't exist in the next +frame shown to the user. Components only return a new value if an user +interaction produced a change. Additionally, each component produces a +number of shapes & text labels, all of which are collected internally +and are, from the user's POV, a mere side effect. At the end of the +update cycle IMGUI produces a tree of +[@thi.ng/hdom-canvas](https://github.com/thi-ng/umbrella/tree/master/packages/hdom-canvas) +compatible elements, which can be easily converted into other formats +(incl. SVG). + +*Note: The WebGL conversion still in the early stages and not yet +published, pending ongoing development in other packages...* + +### Current features + +- No direct user state mutation (unlike most other IMGUI impls) +- Flexible & nestable grid layout with support for cell-spans +- Theme stack for scoped theme switches / overrides +- Stack for scoped disabled GUI elements & to create modals +- Hashing & caching of component local state & draws shapes / resources +- Hover-based mouse cursor overrides +- Hover tooltips +- Re-usable hover & activation behaviors (for creating new components) +- Fully keyboard controllable & Tab-focus switching / highlighting +- All built-in components based on + [@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/master/packages/geom) + shape primitives + +### Available components / widgets + +The above screenshot shows most of the currently available components: + +- Push button (horizontal / vertical) +- Icon button (w/ opt text label) +- 2x dial types & dial groups (h / v) +- Dropdown +- Radial menu +- Radio button group (h / v) +- Slider & slider groups (h / v) +- Text input (single line, filtered input) +- Text label +- Toggle button +- XY pad + +All components are: + +- Skinnable (via theme) +- Keyboard controllable (incl. focus switching) +- Support tooltips + +### State handling + +All built-in components only return a result value if the component was +interacted with and would result in a state change (i.e. a slider has +been dragged or button pressed). So, unlike the traditional IMGUI +pattern (esp. in languages with pointer support), none of the components +here directly manipulate user state and this task is left entirely to +the user. This results in somewhat *slightly* more verbose code, but +offers complete freedom WRT how user state is & can be organized. Also, +things like undo / redo become easier to handle this way. + +```ts +// example state (see @thi.ng/atom) +const STATE = new History(new Atom({ foo: true })); + +... +// get atom snapshot +const curr = STATE.deref(); + +// toggle component will only return result if user clicked it +let res = toggle(gui, layout, "foo", curr.foo, false, curr.foo ? "ON" : "OFF"); +// conditional immutable update (w/ automatic undo snapshot) +res !== undefined && STATE.resetIn("foo", res); +``` + +### Layout support + +Most component functions exist in two versions: Using a layout manager +or not (`Raw` suffix, e.g. `buttonRaw`). The latter versions are more +"low-level" & verbose to use, but offer complete layout freedom and are +re-used by other component types. + +Currently, this package features only a single grid layout type, but +components are not hard-coded to require it, and those which do need a +layout manager only expect a `ILayout` or `IGridLayout` interface, +allowing for custom implementations. Furthermore / alternatively, we +also define a simple [`LayoutBox` +interface](https://github.com/thi-ng/umbrella/tree/master/packages/imgui/src/api.ts), +which can be passed instead and too is what `ILayout` implementations +are expected to produce when allocating space for a component. + +The `GridLayout` class supports infinite nesting and column/row-based +space allocation, based on an initial configuration and supporting +multiple column/row spans. + +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/imgui/imgui-layout.png) + +The code producing this structure: + +```ts +// create a single column layout @ position 10,10 / 200px wide +// the last values are row height and cell spacing +const layout = gridLayout(10, 10, 200, 1, 16, 4); + +// get next layout box (1st row) +// usually you don't need to call .next() manually, but merely pass +// the layout instance to a component... +layout.next(); +// { x: 10, y: 10, w: 200, h: 16, cw: 200, ch: 16, gap: 4 } + +// 2nd row +layout.next(); +// { x: 10, y: 30, w: 200, h: 16, cw: 200, ch: 16, gap: 4 } + +// create nested 2-column layout (3rd row) +const twoCols = layout.nest(2); + +twoCols.next(); +// { x: 10, y: 50, w: 98, h: 16, cw: 98, ch: 16, gap: 4 } + +twoCols.next(); +// { x: 112, y: 50, w: 98, h: 16, cw: 98, ch: 16, gap: 4 } + +// now nest 3-columns in the 1st column of twoCols +// (i.e. now each column is 1/6th of the main layout's width) +const inner = twoCols.nest(3); + +// allocate with col/rowspan, here 1 column x 4 rows +inner.next([1, 4]) +// { x: 10, y: 70, w: 30, h: 76, cw: 30, ch: 16, gap: 4 } +inner.next([1, 4]) +// { x: 44, y: 70, w: 30, h: 76, cw: 30, ch: 16, gap: 4 } +inner.next([1, 4]) +// { x: 78, y: 70, w: 30, h: 76, cw: 30, ch: 16, gap: 4 } + +// back to twoCols (2nd column) +twoCols.next([1, 2]); +// { x: 112, y: 70, w: 98, h: 36, cw: 98, ch: 16, gap: 4 } +``` + +### Key controls + +The entire UI is fully keyboard controllable, built-in behaviors: + +| Keys | Scope | Description | +|-----------------------------|------------------|-------------------------------| +| `Tab` / `Shift+Tab` | Global | Switch focus | +| `Enter` / `Space` | Global | Activate focused button | +| `Up` / `Down` or drag mouse | Slider, Dial, XY | Adjust value | +| `Shift+Up/Down` | Slider, Dial, XY | Adjust value (5x step) | +| `Left/Right` | Radial menu | Navigate menu CW/CCW | +| `Left/Right` | Textfield | Move cursor to prev/next word | +| `Left/Right` | XY | Adjust X value | +| `Alt+Left/Right` | Textfield | Move cursor to prev/next word | + +More complex behaviors can be achieved in user land. E.g. in the +[demo](https://github.com/thi-ng/umbrella/tree/master/examples/imgui/), +holding down `Alt` whilst adjusting a slider or dial group will set all +values uniformly... + +### Current limitations + +Some of the most obvious missing features: + +- [ ] variable width font support (currently monospace only) +- [ ] more granular theme options +- [ ] theme-aware layouting (font size, padding etc.) +- [ ] image / texture support (Tex ID abstraction) +- [ ] windows / element containers +- [ ] menu / tree components +- [ ] scrolling / clipping +- [ ] drag & drop + +### Status + +WIP - Alpha. *hic sunt dracones etc.* + +## Installation + +```bash +yarn add @thi.ng/imgui +``` + +## Dependencies + +- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) +- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/master/packages/checks) +- [@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/master/packages/geom) +- [@thi.ng/math](https://github.com/thi-ng/umbrella/tree/master/packages/math) +- [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) +- [@thi.ng/vectors](https://github.com/thi-ng/umbrella/tree/master/packages/vectors) + +## Usage examples + +Documented WIP demo GUI with undo/redo, on-demand updates and showcasing +all available components (see above screenshot): + +[Live demo](http://demo.thi.ng/umbrella/imgui/) | [Source +code](https://github.com/thi-ng/umbrella/tree/master/examples/imgui/) + +```ts +import * as imgui from "@thi.ng/imgui"; +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2019 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/imgui/package.json b/packages/imgui/package.json new file mode 100644 index 0000000000..6ece98e50d --- /dev/null +++ b/packages/imgui/package.json @@ -0,0 +1,59 @@ +{ + "name": "@thi.ng/imgui", + "version": "0.1.4", + "description": "Immediate mode GUI with flexible state handling & data only shape output", + "module": "./index.js", + "main": "./lib/index.js", + "umd:main": "./lib/index.umd.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/imgui", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module", + "build:release": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module all", + "build:es6": "tsc --declaration", + "build:test": "rimraf build && tsc -p test/tsconfig.json", + "test": "yarn build:test && mocha build/test/*.js", + "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib behaviors components", + "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", + "pub": "yarn build:release && yarn publish --access public" + }, + "devDependencies": { + "@types/mocha": "^5.2.6", + "@types/node": "^12.6.3", + "mocha": "^6.1.4", + "nyc": "^14.0.0", + "typedoc": "^0.15.0", + "typescript": "^3.6.4" + }, + "dependencies": { + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/geom": "^1.7.7", + "@thi.ng/geom-api": "^0.3.7", + "@thi.ng/geom-isec": "^0.3.9", + "@thi.ng/geom-tessellate": "^0.2.9", + "@thi.ng/math": "^1.5.0", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vectors": "^4.0.0" + }, + "keywords": [ + "canvas", + "components", + "ES6", + "GUI", + "IMGUI", + "immediate mode", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "sideEffects": false +} diff --git a/packages/imgui/src/api.ts b/packages/imgui/src/api.ts new file mode 100644 index 0000000000..10276a2bf1 --- /dev/null +++ b/packages/imgui/src/api.ts @@ -0,0 +1,241 @@ +import { Predicate } from "@thi.ng/api"; +import { ReadonlyVec } from "@thi.ng/vectors"; + +export type Color = string | number | number[]; + +export type Hash = number; + +export interface GUITheme { + globalBg?: Color; + font?: string; + fontSize: number; + charWidth: number; + baseLine: number; + pad: number; + focus: Color; + cursor: Color; + cursorBlink: number; + bg: Color; + bgDisabled: Color; + bgHover: Color; + fg: Color; + fgDisabled: Color; + fgHover: Color; + text: Color; + textDisabled: Color; + textHover: Color; + bgTooltip: Color; + textTooltip: Color; +} + +export interface IMGUIOpts { + theme?: Partial; +} + +export interface LayoutBox { + /** + * Top-left corner X + */ + x: number; + /** + * Top-left corner Y + */ + y: number; + /** + * Box width (based on requested col span and inner gutter(s)) + */ + w: number; + /** + * Box height (based on requested row span and inner gutter(s)) + */ + h: number; + /** + * Single cell column width (always w/o col span), based on + * layout's available space and configured number of columns. + */ + cw: number; + /** + * Single cell row height (always same as `rowHeight` arg given to + * layout ctor). + */ + ch: number; + /** + * Gutter size. + */ + gap: number; +} + +export interface ILayout { + next(opts?: O): T; +} + +export interface IGridLayout extends ILayout<[number, number], LayoutBox> { + readonly x: number; + readonly y: number; + readonly width: number; + readonly cols: number; + readonly cellW: number; + readonly cellH: number; + readonly gap: number; + + /** + * Returns the number of columns for given width. + * + * @param w + */ + colsForWidth(w: number): number; + + /** + * Returns the number of rows for given height. + * + * @param w + */ + rowsForHeight(h: number): number; + + /** + * Calculates the required number of columns & rows for the given + * size. + * + * @param size + */ + spansForSize(size: ReadonlyVec): [number, number]; + spansForSize(w: number, h: number): [number, number]; + + /** + * Returns a squared `LayoutBox` based on this layout's column + * width. This box will consume `ceil(columnWidth / rowHeight)` + * rows, but the returned box height might be less to satisfy the + * square constraint. + */ + nextSquare(): LayoutBox; + + /** + * Requests a `spans` sized cell from this layout (via `.next()`) + * and creates and returns a new child `GridLayout` for the returned + * box / grid cell. This child layout is configured to use `cols` + * columns and shares same `gap` as this (parent) layout. The + * configured row span only acts as initial minimum vertical space + * reseervation, but is allowed to grow and if needed will propagate + * the new space requirements to parent layouts. + * + * Note: this size child-parent size propagation ONLY works until + * the next cell is requested from any parent. IOW, child layouts + * MUST be completed/populated first before continuing with + * siblings/ancestors of this current layout. + * + * ``` + * // single column layout (default config) + * const outer = gridLayout(null, 0, 0, 200, 1, 16, 4); + * + * // add button (full 1st row) + * button(gui, outer, "foo",...); + * + * // 2-column nested layout (2nd row) + * const inner = outer.nest(2) + * // these buttons are on same row + * button(gui, inner, "bar",...); + * button(gui, inner, "baz",...); + * + * // continue with outer, create empty row + * outer.next(); + * + * // continue with outer (4th row) + * button(gui, outer, "bye",...); + * ``` + * + * @param cols columns in nested layout + * @param spans default [1, 1] (i.e. size of single cell) + */ + nest(cols: number, spans?: [number, number]): IGridLayout; +} + +export const enum MouseButton { + LEFT = 1, + RIGHT = 2, + MIDDLE = 4 +} + +export const enum KeyModifier { + SHIFT = 1, + CONTROL = 2, + META = 4, + ALT = 8 +} + +export const enum Key { + ALT = "Alt", + BACKSPACE = "Backspace", + CAPSLOCK = "CapsLock", + CONTEXT_MENU = "ContextMenu", + CONTROL = "Control", + DELETE = "Delete", + DOWN = "ArrowDown", + END = "End", + ENTER = "Enter", + ESC = "Escape", + HELP = "Help", + HOME = "Home", + LEFT = "ArrowLeft", + META = "Meta", + NUM_LOCK = "NumLock", + PAGE_DOWN = "PageDown", + PAGE_UP = "PageUp", + RIGHT = "ArrowRight", + SHIFT = "Shift", + SPACE = " ", + TAB = "Tab", + UP = "ArrowUp" +} + +export const CONTROL_KEYS = new Set([ + Key.ALT, + Key.BACKSPACE, + Key.CAPSLOCK, + Key.CONTEXT_MENU, + Key.CONTROL, + Key.DELETE, + Key.DOWN, + Key.END, + Key.ENTER, + Key.ESC, + Key.HELP, + Key.HOME, + Key.LEFT, + Key.META, + Key.NUM_LOCK, + Key.PAGE_DOWN, + Key.PAGE_UP, + Key.RIGHT, + Key.SHIFT, + Key.TAB, + Key.UP +]); + +export const NONE = "__NONE__"; + +export const DEFAULT_THEME: GUITheme = { + font: "10px Menlo, monospace", + fontSize: 10, + charWidth: 6, + baseLine: 4, + pad: 8, + globalBg: "#ccc", + focus: [0, 1, 0, 1], + cursor: [0, 0, 0, 1], + cursorBlink: 2, + bg: [1, 1, 1, 0.66], + bgDisabled: [1, 1, 1, 0.33], + bgHover: [1, 1, 1, 0.9], + fg: [0.2, 0.8, 1, 1], + fgDisabled: [0.2, 0.8, 1, 0.5], + fgHover: [0.3, 0.9, 1, 1], + text: [0.3, 0.3, 0.3, 1], + textDisabled: [0.3, 0.3, 0.3, 0.5], + textHover: [0.2, 0.2, 0.4, 1], + bgTooltip: [1, 1, 0.8, 0.85], + textTooltip: [0, 0, 0, 1] +}; + +export const INPUT_ALPHA: Predicate = (x) => /^\w$/.test(x); + +export const INPUT_DIGITS: Predicate = (x) => /^\d$/.test(x); diff --git a/packages/imgui/src/behaviors/button.ts b/packages/imgui/src/behaviors/button.ts new file mode 100644 index 0000000000..4432d9a8bd --- /dev/null +++ b/packages/imgui/src/behaviors/button.ts @@ -0,0 +1,27 @@ +import { pointInside } from "@thi.ng/geom"; +import { IShape } from "@thi.ng/geom-api"; +import { Key } from "../api"; +import { IMGUI } from "../gui"; + +export const isHoverButton = (gui: IMGUI, id: string, shape: IShape) => { + if (gui.disabled) return false; + const aid = gui.activeID; + const hover = (aid === "" || aid === id) && pointInside(shape, gui.mouse); + if (hover) { + gui.setCursor("pointer"); + gui.hotID = id; + } + return hover; +}; + +export const handleButtonKeys = (gui: IMGUI) => { + switch (gui.key) { + case Key.TAB: + gui.switchFocus(); + break; + case Key.ENTER: + case Key.SPACE: + return true; + default: + } +}; diff --git a/packages/imgui/src/behaviors/dial.ts b/packages/imgui/src/behaviors/dial.ts new file mode 100644 index 0000000000..bb17f40cc2 --- /dev/null +++ b/packages/imgui/src/behaviors/dial.ts @@ -0,0 +1,22 @@ +import { fit, TAU } from "@thi.ng/math"; +import { heading, ReadonlyVec, sub2 } from "@thi.ng/vectors"; +import { slider1Val } from "./slider"; + +export const dialVal = ( + p: ReadonlyVec, + c: ReadonlyVec, + startTheta: number, + thetaGap: number, + min: number, + max: number, + prec: number +) => { + let theta = heading(sub2([], p, c)) - startTheta; + theta < -0.5 && (theta += TAU); + return slider1Val( + fit(Math.min(theta / (TAU - thetaGap)), 0, 1, min, max), + min, + max, + prec + ); +}; diff --git a/packages/imgui/src/behaviors/slider.ts b/packages/imgui/src/behaviors/slider.ts new file mode 100644 index 0000000000..3dd3b42d06 --- /dev/null +++ b/packages/imgui/src/behaviors/slider.ts @@ -0,0 +1,86 @@ +import { pointInside } from "@thi.ng/geom"; +import { IShape } from "@thi.ng/geom-api"; +import { clamp, roundTo } from "@thi.ng/math"; +import { + add2, + clamp2, + round2, + Vec +} from "@thi.ng/vectors"; +import { Key } from "../api"; +import { IMGUI } from "../gui"; + +export const isHoverSlider = ( + gui: IMGUI, + id: string, + shape: IShape, + cursor = "ew-resize" +) => { + if (gui.disabled) return false; + const aid = gui.activeID; + const hover = aid === id || (aid === "" && pointInside(shape, gui.mouse)); + if (hover) { + gui.setCursor(cursor); + gui.hotID = id; + } + return hover; +}; + +export const slider1Val = (x: number, min: number, max: number, prec: number) => + clamp(roundTo(x, prec), min, max); + +export const slider2Val = (v: Vec, min: Vec, max: Vec, prec: number) => + clamp2(null, round2([], v, prec), min, max); + +export const handleSlider1Keys = ( + gui: IMGUI, + min: number, + max: number, + prec: number, + val: number +) => { + switch (gui.key) { + case Key.TAB: + gui.switchFocus(); + break; + case Key.UP: + case Key.DOWN: { + const step = + (gui.key === Key.UP ? prec : -prec) * + (gui.isShiftDown() ? 5 : 1); + return slider1Val(val + step, min, max, prec); + } + default: + } +}; + +export const handleSlider2Keys = ( + gui: IMGUI, + min: Vec, + max: Vec, + prec: number, + val: Vec, + yUp: boolean +) => { + switch (gui.key) { + case Key.TAB: + gui.switchFocus(); + break; + case Key.LEFT: + case Key.RIGHT: { + const step = + (gui.key === Key.RIGHT ? prec : -prec) * + (gui.isShiftDown() ? 5 : 1); + return slider2Val(add2([], val, [step, 0]), min, max, prec); + } + case Key.UP: + case Key.DOWN: { + const step = + (gui.key === Key.UP ? prec : -prec) * + (yUp ? 1 : -1) * + (gui.isShiftDown() ? 5 : 1); + return slider2Val(add2([], val, [0, step]), min, max, prec); + } + default: + } +}; diff --git a/packages/imgui/src/components/button.ts b/packages/imgui/src/components/button.ts new file mode 100644 index 0000000000..2222c41a4a --- /dev/null +++ b/packages/imgui/src/components/button.ts @@ -0,0 +1,129 @@ +import { rect } from "@thi.ng/geom"; +import { IShape } from "@thi.ng/geom-api"; +import { hash, ZERO2 } from "@thi.ng/vectors"; +import { + Color, + Hash, + IGridLayout, + LayoutBox +} from "../api"; +import { handleButtonKeys, isHoverButton } from "../behaviors/button"; +import { IMGUI } from "../gui"; +import { labelHash } from "../hash"; +import { isLayout } from "../layout"; +import { textLabelRaw, textTransformH, textTransformV } from "./textlabel"; +import { tooltipRaw } from "./tooltip"; + +const mkLabel = (transform: number[], fill: Color, label: string) => + textLabelRaw(ZERO2, { transform, fill }, label); + +export const buttonH = ( + gui: IMGUI, + layout: IGridLayout | LayoutBox, + id: string, + label?: string, + labelHover = label, + info?: string +) => { + const theme = gui.theme; + const { x, y, w, h } = isLayout(layout) ? layout.next() : layout; + const key = hash([x, y, w, h]); + return buttonRaw( + gui, + id, + gui.resource(id, key, () => rect([x, y], [w, h])), + key, + label + ? gui.resource(id, labelHash(key, label, gui.disabled), () => + mkLabel( + textTransformH(theme, x, y, w, h), + gui.textColor(false), + label + ) + ) + : undefined, + labelHover + ? gui.resource(id, labelHash(key, labelHover, gui.disabled), () => + mkLabel( + textTransformH(theme, x, y, w, h), + gui.textColor(true), + labelHover + ) + ) + : undefined, + info + ); +}; + +export const buttonV = ( + gui: IMGUI, + layout: IGridLayout | LayoutBox, + id: string, + rows: number, + label?: string, + labelHover = label, + info?: string +) => { + const theme = gui.theme; + const { x, y, w, h } = isLayout(layout) ? layout.next([1, rows]) : layout; + const key = hash([x, y, w, h]); + return buttonRaw( + gui, + id, + gui.resource(id, key, () => rect([x, y], [w, h])), + key, + label + ? gui.resource(id, labelHash(key, label, gui.disabled), () => + mkLabel( + textTransformV(theme, x, y, w, h), + gui.textColor(false), + label + ) + ) + : undefined, + labelHover + ? gui.resource(id, labelHash(key, labelHover, gui.disabled), () => + mkLabel( + textTransformV(theme, x, y, w, h), + gui.textColor(true), + labelHover + ) + ) + : undefined, + info + ); +}; + +export const buttonRaw = ( + gui: IMGUI, + id: string, + shape: IShape, + hash: Hash, + label?: any, + labelHover?: any, + info?: string +) => { + gui.registerID(id, hash); + const hover = isHoverButton(gui, id, shape); + const draw = gui.draw; + if (hover) { + gui.isMouseDown() && (gui.activeID = id); + info && draw && tooltipRaw(gui, info); + } + const focused = gui.requestFocus(id); + if (draw) { + shape.attribs = { + fill: hover ? gui.fgColor(true) : gui.bgColor(focused), + stroke: gui.focusColor(id) + }; + gui.add(shape); + label && gui.add(hover && labelHover ? labelHover : label); + } + if (focused && handleButtonKeys(gui)) { + return true; + } + gui.lastID = id; + // only emit true on mouse release over this button + // TODO extract as behavior function + return !gui.buttons && gui.hotID === id && gui.activeID === id; +}; diff --git a/packages/imgui/src/components/dial.ts b/packages/imgui/src/components/dial.ts new file mode 100644 index 0000000000..a68a8fe8d3 --- /dev/null +++ b/packages/imgui/src/components/dial.ts @@ -0,0 +1,169 @@ +import { Fn } from "@thi.ng/api"; +import { circle, line } from "@thi.ng/geom"; +import { + HALF_PI, + norm, + PI, + TAU +} from "@thi.ng/math"; +import { cartesian2, hash } from "@thi.ng/vectors"; +import { IGridLayout, LayoutBox } from "../api"; +import { dialVal } from "../behaviors/dial"; +import { handleSlider1Keys, isHoverSlider } from "../behaviors/slider"; +import { IMGUI } from "../gui"; +import { valHash } from "../hash"; +import { isLayout } from "../layout"; +import { textLabelRaw } from "./textlabel"; +import { tooltipRaw } from "./tooltip"; + +export const dial = ( + gui: IMGUI, + layout: IGridLayout | LayoutBox, + id: string, + min: number, + max: number, + prec: number, + val: number, + label?: string, + fmt?: Fn, + info?: string +) => { + const { x, y, w, h, ch } = isLayout(layout) ? layout.nextSquare() : layout; + return dialRaw( + gui, + id, + x, + y, + w, + h, + min, + max, + prec, + val, + gui.theme.pad, + h + ch / 2 + gui.theme.baseLine, + label, + fmt, + info + ); +}; + +export const dialGroup = ( + gui: IMGUI, + layout: IGridLayout, + id: string, + min: number, + max: number, + prec: number, + horizontal: boolean, + vals: number[], + label: string[], + fmt?: Fn, + info: string[] = [] +) => { + const n = vals.length; + const nested = horizontal + ? layout.nest(n, [n, 1]) + : layout.nest(1, [1, (layout.rowsForHeight(layout.cellW) + 1) * n]); + let res: number | undefined; + let idx: number = -1; + for (let i = 0; i < n; i++) { + const v = dial( + gui, + nested, + `${id}-${i}`, + min, + max, + prec, + vals[i], + label[i], + fmt, + info[i] + ); + if (v !== undefined) { + res = v; + idx = i; + } + } + return res !== undefined ? [idx, res] : undefined; +}; + +export const dialRaw = ( + gui: IMGUI, + id: string, + x: number, + y: number, + w: number, + h: number, + min: number, + max: number, + prec: number, + val: number, + lx: number, + ly: number, + label?: string, + fmt?: Fn, + info?: string +) => { + const r = Math.min(w, h) / 2; + const pos = [x + w / 2, y + h / 2]; + const thetaGap = PI / 2; + const startTheta = HALF_PI + thetaGap / 2; + const key = hash([x, y, r]); + gui.registerID(id, key); + const bgShape = gui.resource(id, key, () => circle(pos, r, {})); + const hover = isHoverSlider(gui, id, bgShape, "pointer"); + const draw = gui.draw; + let v: number | undefined = val; + let res: number | undefined; + if (hover) { + gui.hotID = id; + if (gui.isMouseDown()) { + gui.activeID = id; + res = v = dialVal( + gui.mouse, + pos, + startTheta, + thetaGap, + min, + max, + prec + ); + } + info && draw && tooltipRaw(gui, info); + } + const focused = gui.requestFocus(id); + if (draw) { + const valShape = gui.resource(id, v, () => + line( + cartesian2( + null, + [r, startTheta + (TAU - thetaGap) * norm(v!, min, max)], + pos + ), + pos, + {} + ) + ); + const valLabel = gui.resource(id, valHash(key, v, gui.disabled), () => + textLabelRaw( + [x + lx, y + ly], + gui.textColor(false), + (label ? label + " " : "") + (fmt ? fmt(v!) : v) + ) + ); + bgShape.attribs.fill = gui.bgColor(hover || focused); + bgShape.attribs.stroke = gui.focusColor(id); + valShape.attribs.stroke = gui.fgColor(hover); + valShape.attribs.weight = 2; + gui.add(bgShape, valShape, valLabel); + } + if ( + focused && + (v = handleSlider1Keys(gui, min, max, prec, v)) !== undefined + ) { + return v; + } + gui.lastID = id; + return res; +}; diff --git a/packages/imgui/src/components/dropdown.ts b/packages/imgui/src/components/dropdown.ts new file mode 100644 index 0000000000..820660cc43 --- /dev/null +++ b/packages/imgui/src/components/dropdown.ts @@ -0,0 +1,99 @@ +import { polygon } from "@thi.ng/geom"; +import { hash } from "@thi.ng/vectors"; +import { IGridLayout, Key, LayoutBox } from "../api"; +import { IMGUI } from "../gui"; +import { gridLayout, isLayout } from "../layout"; +import { buttonH } from "./button"; + +/** + * + * @param gui + * @param layout + * @param id + * @param sel + * @param items + * @param title + * @param info + */ +export const dropdown = ( + gui: IMGUI, + layout: IGridLayout | LayoutBox, + id: string, + sel: number, + items: string[], + title: string, + info?: string +) => { + const open = gui.state(id, () => false); + const nested = isLayout(layout) + ? layout.nest(1, [1, open ? items.length : 1]) + : gridLayout(layout.x, layout.y, layout.w, 1, layout.ch, layout.gap); + let res: number | undefined; + const box = nested.next(); + const { x, y, w, h } = box; + const key = hash([x, y, w, h, ~~gui.disabled]); + const tx = x + w - gui.theme.pad - 4; + const ty = y + h / 2; + const draw = gui.draw; + if (open) { + const bt = buttonH(gui, box, `${id}-title`, title); + draw && + gui.add( + gui.resource(id, key + 1, () => + polygon( + [[tx - 4, ty + 2], [tx + 4, ty + 2], [tx, ty - 2]], + { + fill: gui.textColor(false) + } + ) + ) + ); + if (bt) { + gui.setState(id, false); + } else { + for (let i = 0, n = items.length; i < n; i++) { + if (buttonH(gui, nested, `${id}-${i}`, items[i])) { + i !== sel && (res = i); + gui.setState(id, false); + } + } + if (gui.focusID.startsWith(`${id}-`)) { + switch (gui.key) { + case Key.ESC: + gui.setState(id, false); + break; + case Key.UP: + return update(gui, id, Math.max(0, sel - 1)); + case Key.DOWN: + return update( + gui, + id, + Math.min(items.length - 1, sel + 1) + ); + default: + } + } + } + } else { + if (buttonH(gui, box, `${id}-${sel}`, items[sel], title, info)) { + gui.setState(id, true); + } + draw && + gui.add( + gui.resource(id, key + 2, () => + polygon( + [[tx - 4, ty - 2], [tx + 4, ty - 2], [tx, ty + 2]], + { + fill: gui.textColor(false) + } + ) + ) + ); + } + return res; +}; + +const update = (gui: IMGUI, id: string, next: number) => { + gui.focusID = `${id}-${next}`; + return next; +}; diff --git a/packages/imgui/src/components/icon-button.ts b/packages/imgui/src/components/icon-button.ts new file mode 100644 index 0000000000..2d2e81cabd --- /dev/null +++ b/packages/imgui/src/components/icon-button.ts @@ -0,0 +1,59 @@ +import { rect } from "@thi.ng/geom"; +import { hash } from "@thi.ng/vectors"; +import { IGridLayout, LayoutBox } from "../api"; +import { IMGUI } from "../gui"; +import { mixHash } from "../hash"; +import { isLayout } from "../layout"; +import { buttonRaw } from "./button"; +import { textLabelRaw } from "./textlabel"; + +export const iconButton = ( + gui: IMGUI, + layout: IGridLayout | LayoutBox, + id: string, + icon: any, + iconW: number, + iconH: number, + label?: string, + info?: string +) => { + const theme = gui.theme; + const pad = theme.pad; + const bodyW = label + ? iconW + 3 * pad + gui.textWidth(label) + : iconW + 2 * pad; + const bodyH = iconH + pad; + const { x, y, w, h } = isLayout(layout) + ? layout.next(layout.spansForSize(bodyW, bodyH)) + : layout; + const key = hash([x, y, w, h, ~~gui.disabled]); + const mkIcon = (hover: boolean) => { + const col = gui.textColor(hover); + const pos = [x + pad, y + (h - iconH) / 2]; + return [ + "g", + { + translate: pos, + fill: col, + stroke: col + }, + icon, + label + ? textLabelRaw( + [iconW + pad, -(h - iconH) / 2 + h / 2 + theme.baseLine], + { fill: col, stroke: "none" }, + label + ) + : undefined + ]; + }; + return buttonRaw( + gui, + id, + gui.resource(id, key, () => rect([x, y], [w, h])), + key, + gui.resource(id, mixHash(key, `l${label}`), () => mkIcon(false)), + gui.resource(id, mixHash(key, `lh${label}`), () => mkIcon(true)), + info + ); +}; diff --git a/packages/imgui/src/components/radial-menu.ts b/packages/imgui/src/components/radial-menu.ts new file mode 100644 index 0000000000..e25fb37792 --- /dev/null +++ b/packages/imgui/src/components/radial-menu.ts @@ -0,0 +1,67 @@ +import { + centroid, + circle, + polygon, + Polygon, + vertices +} from "@thi.ng/geom"; +import { triFan } from "@thi.ng/geom-tessellate"; +import { fmod } from "@thi.ng/math"; +import { mapIndexed } from "@thi.ng/transducers"; +import { add2, hash } from "@thi.ng/vectors"; +import { Hash, Key } from "../api"; +import { IMGUI } from "../gui"; +import { buttonRaw } from "./button"; +import { textLabelRaw } from "./textlabel"; + +export const radialMenu = ( + gui: IMGUI, + id: string, + x: number, + y: number, + r: number, + items: string[], + info: string[] +) => { + const n = items.length; + const key = hash([x, y, r, n, ~~gui.disabled]); + gui.registerID(id, key); + const cells: [Polygon, Hash, any, any][] = gui.resource(id, key, () => [ + ...mapIndexed((i, pts) => { + const cell = polygon(pts); + const p = add2( + null, + [-gui.textWidth(items[i]) >> 1, gui.theme.baseLine], + centroid(cell)! + ); + return [ + cell, + hash(p), + textLabelRaw(p, gui.textColor(false), items[i]), + textLabelRaw(p, gui.textColor(true), items[i]) + ]; + }, triFan(vertices(circle([x, y], r), n))) + ]); + let res: number | undefined; + let sel = -1; + for (let i = 0; i < n; i++) { + const cell = cells[i]; + const _id = id + i; + buttonRaw(gui, _id, cell[0], cell[1], cell[2], cell[3], info[i]) && + (res = i); + gui.focusID === _id && (sel = i); + } + if (sel !== -1) { + switch (gui.key) { + case Key.UP: + case Key.RIGHT: + gui.focusID = id + fmod(sel + 1, n); + break; + case Key.DOWN: + case Key.LEFT: + gui.focusID = id + fmod(sel - 1, n); + default: + } + } + return res; +}; diff --git a/packages/imgui/src/components/radio.ts b/packages/imgui/src/components/radio.ts new file mode 100644 index 0000000000..ad52eed757 --- /dev/null +++ b/packages/imgui/src/components/radio.ts @@ -0,0 +1,37 @@ +import { IGridLayout, LayoutBox } from "../api"; +import { IMGUI } from "../gui"; +import { gridLayout, isLayout } from "../layout"; +import { toggle } from "./toggle"; + +export const radio = ( + gui: IMGUI, + layout: IGridLayout | LayoutBox, + id: string, + horizontal: boolean, + sel: number, + square: boolean, + labels: string[], + info: string[] = [] +) => { + const n = labels.length; + const nested = isLayout(layout) + ? horizontal + ? layout.nest(n, [n, 1]) + : layout.nest(1, [1, n]) + : horizontal + ? gridLayout(layout.x, layout.y, layout.w, n, layout.ch, layout.gap) + : gridLayout(layout.x, layout.y, layout.w, 1, layout.ch, layout.gap); + let res: number | undefined; + for (let i = 0; i < n; i++) { + toggle( + gui, + nested, + `${id}-${i}`, + sel === i, + square, + labels[i], + info[i] + ) !== undefined && (res = i); + } + return res; +}; diff --git a/packages/imgui/src/components/ring.ts b/packages/imgui/src/components/ring.ts new file mode 100644 index 0000000000..dc722e92dc --- /dev/null +++ b/packages/imgui/src/components/ring.ts @@ -0,0 +1,226 @@ +import { Fn } from "@thi.ng/api"; +import { polygon } from "@thi.ng/geom"; +import { pointInRect } from "@thi.ng/geom-isec"; +import { + fitClamped, + HALF_PI, + mix, + norm, + PI, + TAU +} from "@thi.ng/math"; +import { map, normRange } from "@thi.ng/transducers"; +import { cartesian2, hash, Vec } from "@thi.ng/vectors"; +import { IGridLayout, LayoutBox } from "../api"; +import { dialVal } from "../behaviors/dial"; +import { handleSlider1Keys } from "../behaviors/slider"; +import { IMGUI } from "../gui"; +import { valHash } from "../hash"; +import { isLayout } from "../layout"; +import { textLabelRaw } from "./textlabel"; +import { tooltipRaw } from "./tooltip"; + +const ringHeight = (w: number, thetaGap: number) => + (w / 2) * (1 + Math.sin(HALF_PI + thetaGap / 2)); + +const arcVerts = ( + o: Vec, + r: number, + start: number, + end: number, + thetaRes = 12 +): Iterable => + r > 1 + ? map( + (t) => cartesian2(null, [r, mix(start, end, t)], o), + normRange( + Math.max(1, Math.abs(end - start) / (PI / thetaRes)) | 0 + ) + ) + : [o]; + +export const ring = ( + gui: IMGUI, + layout: IGridLayout | LayoutBox, + id: string, + min: number, + max: number, + prec: number, + val: number, + thetaGap: number, + rscale: number, + label?: string, + fmt?: Fn, + info?: string +) => { + let h: number; + let box: LayoutBox; + if (isLayout(layout)) { + h = ringHeight(layout.cellW, thetaGap); + box = layout.next([1, layout.rowsForHeight(h) + 1]); + } else { + h = ringHeight(layout.cw, thetaGap); + box = layout; + } + return ringRaw( + gui, + id, + box.x, + box.y, + box.w, + h, + min, + max, + prec, + val, + thetaGap, + rscale, + 0, + h + box.ch / 2 + gui.theme.baseLine, + label, + fmt, + info + ); +}; + +export const ringGroup = ( + gui: IMGUI, + layout: IGridLayout, + id: string, + min: number, + max: number, + prec: number, + horizontal: boolean, + thetaGap: number, + rscale: number, + vals: number[], + label: string[], + fmt?: Fn, + info: string[] = [] +) => { + const n = vals.length; + const nested = horizontal + ? layout.nest(n, [n, 1]) + : layout.nest(1, [ + 1, + (layout.rowsForHeight(ringHeight(layout.cellW, thetaGap)) + 1) * n + ]); + let res: number | undefined; + let idx: number = -1; + for (let i = 0; i < n; i++) { + const v = ring( + gui, + nested, + `${id}-${i}`, + min, + max, + prec, + vals[i], + thetaGap, + rscale, + label[i], + fmt, + info[i] + ); + if (v !== undefined) { + res = v; + idx = i; + } + } + return res !== undefined ? [idx, res] : undefined; +}; + +export const ringRaw = ( + gui: IMGUI, + id: string, + x: number, + y: number, + w: number, + h: number, + min: number, + max: number, + prec: number, + val: number, + thetaGap: number, + rscale: number, + lx: number, + ly: number, + label?: string, + fmt?: Fn, + info?: string +) => { + const r = w / 2; + const key = hash([x, y, r]); + gui.registerID(id, key); + const pos = [x + r, y + r]; + const startTheta = HALF_PI + thetaGap / 2; + const endTheta = HALF_PI + TAU - thetaGap / 2; + const draw = gui.draw; + const aid = gui.activeID; + const hover = + !gui.disabled && + (aid === id || (aid === "" && pointInRect(gui.mouse, [x, y], [w, h]))); + let v: number | undefined = val; + let res: number | undefined; + if (hover) { + gui.setCursor("pointer"); + gui.hotID = id; + if (gui.isMouseDown()) { + gui.activeID = id; + res = v = dialVal( + gui.mouse, + pos, + startTheta, + thetaGap, + min, + max, + prec + ); + } + info && draw && tooltipRaw(gui, info); + } + const focused = gui.requestFocus(id); + if (draw) { + const valTheta = startTheta + (TAU - thetaGap) * norm(v, min, max); + const r2 = r * rscale; + // adaptive arc resolution + const numV = fitClamped(r, 15, 80, 12, 30); + const bgShape = gui.resource(id, key, () => + polygon( + [ + ...arcVerts(pos, r, startTheta, endTheta, numV), + ...arcVerts(pos, r2, endTheta, startTheta, numV) + ], + {} + ) + ); + const valShape = gui.resource(id, v, () => + polygon( + [ + ...arcVerts(pos, r, startTheta, valTheta, numV), + ...arcVerts(pos, r2, valTheta, startTheta, numV) + ], + {} + ) + ); + const valLabel = gui.resource(id, valHash(key, v, gui.disabled), () => + textLabelRaw( + [x + lx, y + ly], + gui.textColor(false), + (label ? label + " " : "") + (fmt ? fmt(v!) : v) + ) + ); + bgShape.attribs.fill = gui.bgColor(hover || focused); + bgShape.attribs.stroke = gui.focusColor(id); + valShape.attribs.fill = gui.fgColor(hover); + gui.add(bgShape, valShape, valLabel); + } + if ( + focused && + (v = handleSlider1Keys(gui, min, max, prec, v)) !== undefined + ) { + return v; + } + gui.lastID = id; + return res; +}; diff --git a/packages/imgui/src/components/sliderh.ts b/packages/imgui/src/components/sliderh.ts new file mode 100644 index 0000000000..370e743621 --- /dev/null +++ b/packages/imgui/src/components/sliderh.ts @@ -0,0 +1,141 @@ +import { Fn } from "@thi.ng/api"; +import { rect } from "@thi.ng/geom"; +import { fit, norm } from "@thi.ng/math"; +import { hash } from "@thi.ng/vectors"; +import { IGridLayout, LayoutBox } from "../api"; +import { handleSlider1Keys, isHoverSlider, slider1Val } from "../behaviors/slider"; +import { IMGUI } from "../gui"; +import { valHash } from "../hash"; +import { isLayout } from "../layout"; +import { textLabelRaw } from "./textlabel"; +import { tooltipRaw } from "./tooltip"; + +export const sliderH = ( + gui: IMGUI, + layout: IGridLayout | LayoutBox, + id: string, + min: number, + max: number, + prec: number, + val: number, + label?: string, + fmt?: Fn, + info?: string +) => { + const box = isLayout(layout) ? layout.next() : layout; + return sliderHRaw( + gui, + id, + box.x, + box.y, + box.w, + box.h, + min, + max, + prec, + val, + label, + fmt, + info + ); +}; + +export const sliderHGroup = ( + gui: IMGUI, + layout: IGridLayout, + id: string, + min: number, + max: number, + prec: number, + horizontal: boolean, + vals: number[], + label: string[], + fmt?: Fn, + info: string[] = [] +) => { + const n = vals.length; + const nested = horizontal ? layout.nest(n, [n, 1]) : layout.nest(1, [1, n]); + let res: number | undefined; + let idx: number = -1; + for (let i = 0; i < n; i++) { + const v = sliderH( + gui, + nested, + `${id}-${i}`, + min, + max, + prec, + vals[i], + label[i], + fmt, + info[i] + ); + if (v !== undefined) { + res = v; + idx = i; + } + } + return res !== undefined ? [idx, res] : undefined; +}; + +export const sliderHRaw = ( + gui: IMGUI, + id: string, + x: number, + y: number, + w: number, + h: number, + min: number, + max: number, + prec: number, + val: number, + label?: string, + fmt?: Fn, + info?: string +) => { + const theme = gui.theme; + const key = hash([x, y, w, h]); + gui.registerID(id, key); + const box = gui.resource(id, key, () => rect([x, y], [w, h], {})); + const hover = isHoverSlider(gui, id, box); + const draw = gui.draw; + let v: number | undefined = val; + let res: number | undefined; + if (hover) { + if (gui.isMouseDown()) { + gui.activeID = id; + res = v = slider1Val( + fit(gui.mouse[0], x, x + w - 1, min, max), + min, + max, + prec + ); + } + info && draw && tooltipRaw(gui, info); + } + const focused = gui.requestFocus(id); + if (draw) { + const valueBox = gui.resource(id, v, () => + rect([x, y], [1 + norm(v!, min, max) * (w - 1), h], {}) + ); + const valLabel = gui.resource(id, valHash(key, v, gui.disabled), () => + textLabelRaw( + [x + theme.pad, y + h / 2 + theme.baseLine], + gui.textColor(false), + (label ? label + " " : "") + (fmt ? fmt(v!) : v) + ) + ); + box.attribs.fill = gui.bgColor(hover || focused); + box.attribs.stroke = gui.focusColor(id); + valueBox.attribs.fill = gui.fgColor(hover); + gui.add(box, valueBox, valLabel); + } + if ( + focused && + (v = handleSlider1Keys(gui, min, max, prec, v)) !== undefined + ) { + return v; + } + gui.lastID = id; + return res; +}; diff --git a/packages/imgui/src/components/sliderv.ts b/packages/imgui/src/components/sliderv.ts new file mode 100644 index 0000000000..e476f47ae1 --- /dev/null +++ b/packages/imgui/src/components/sliderv.ts @@ -0,0 +1,148 @@ +import { Fn } from "@thi.ng/api"; +import { rect } from "@thi.ng/geom"; +import { fit, norm } from "@thi.ng/math"; +import { hash, ZERO2 } from "@thi.ng/vectors"; +import { IGridLayout, LayoutBox } from "../api"; +import { handleSlider1Keys, isHoverSlider, slider1Val } from "../behaviors/slider"; +import { IMGUI } from "../gui"; +import { valHash } from "../hash"; +import { isLayout } from "../layout"; +import { textLabelRaw, textTransformV } from "./textlabel"; +import { tooltipRaw } from "./tooltip"; + +export const sliderV = ( + gui: IMGUI, + layout: IGridLayout | LayoutBox, + id: string, + min: number, + max: number, + prec: number, + val: number, + rows: number, + label?: string, + fmt?: Fn, + info?: string +) => { + const box = isLayout(layout) ? layout.next([1, rows]) : layout; + return sliderVRaw( + gui, + id, + box.x, + box.y, + box.w, + box.h, + min, + max, + prec, + val, + label, + fmt, + info + ); +}; + +export const sliderVGroup = ( + gui: IMGUI, + layout: IGridLayout, + id: string, + min: number, + max: number, + prec: number, + vals: number[], + rows: number, + label: string[], + fmt?: Fn, + info: string[] = [] +) => { + const n = vals.length; + const nested = layout.nest(n, [1, rows]); + let res: number | undefined; + let idx: number = -1; + for (let i = 0; i < n; i++) { + const v = sliderV( + gui, + nested, + `${id}-${i}`, + min, + max, + prec, + vals[i], + rows, + label[i], + fmt, + info[i] + ); + if (v !== undefined) { + res = v; + idx = i; + } + } + return res !== undefined ? [idx, res] : undefined; +}; + +export const sliderVRaw = ( + gui: IMGUI, + id: string, + x: number, + y: number, + w: number, + h: number, + min: number, + max: number, + prec: number, + val: number, + label?: string, + fmt?: Fn, + info?: string +) => { + const theme = gui.theme; + const key = hash([x, y, w, h]); + gui.registerID(id, key); + const box = gui.resource(id, key, () => rect([x, y], [w, h], {})); + const ymax = y + h; + const hover = isHoverSlider(gui, id, box, "ns-resize"); + const draw = gui.draw; + let v: number | undefined = val; + let res: number | undefined; + if (hover) { + if (gui.isMouseDown()) { + gui.activeID = id; + res = v = slider1Val( + fit(gui.mouse[1], ymax - 1, y, min, max), + min, + max, + prec + ); + } + info && draw && tooltipRaw(gui, info); + } + const focused = gui.requestFocus(id); + if (draw) { + const valueBox = gui.resource(id, v, () => { + const nh = norm(v!, min, max) * (h - 1); + return rect([x, ymax - nh], [w, nh], {}); + }); + const valLabel = gui.resource(id, valHash(key, v, gui.disabled), () => + textLabelRaw( + ZERO2, + { + transform: textTransformV(theme, x, y, w, h), + fill: gui.textColor(false) + }, + (label ? label + " " : "") + (fmt ? fmt(v!) : v) + ) + ); + valueBox.attribs.fill = gui.fgColor(hover); + box.attribs.fill = gui.bgColor(hover || focused); + box.attribs.stroke = gui.focusColor(id); + gui.add(box, valueBox, valLabel); + } + if ( + focused && + (v = handleSlider1Keys(gui, min, max, prec, v)) !== undefined + ) { + return v; + } + gui.lastID = id; + return res; +}; diff --git a/packages/imgui/src/components/textfield.ts b/packages/imgui/src/components/textfield.ts new file mode 100644 index 0000000000..5c42285f2e --- /dev/null +++ b/packages/imgui/src/components/textfield.ts @@ -0,0 +1,236 @@ +import { Predicate } from "@thi.ng/api"; +import { rect } from "@thi.ng/geom"; +import { fitClamped } from "@thi.ng/math"; +import { hash } from "@thi.ng/vectors"; +import { IGridLayout, Key, LayoutBox } from "../api"; +import { isHoverSlider } from "../behaviors/slider"; +import { IMGUI } from "../gui"; +import { isLayout } from "../layout"; +import { textLabelRaw } from "./textlabel"; +import { tooltipRaw } from "./tooltip"; + +interface TextfieldState { + cursor: number; + offset: number; +} + +export const textField = ( + gui: IMGUI, + layout: IGridLayout | LayoutBox, + id: string, + label: string, + filter: Predicate = () => true, + info?: string +) => { + const box = isLayout(layout) ? layout.next() : layout; + return textFieldRaw( + gui, + id, + box.x, + box.y, + box.w, + box.h, + label, + filter, + info + ); +}; + +export const textFieldRaw = ( + gui: IMGUI, + id: string, + x: number, + y: number, + w: number, + h: number, + txt: string, + filter: Predicate = () => true, + info?: string +) => { + const theme = gui.theme; + const cw = theme.charWidth; + const pad = theme.pad; + const maxLen = Math.max(1, ((w - pad * 2) / cw) | 0); + const txtLen = txt.length; + const maxOffset = Math.max(0, txtLen - maxLen); + const state = gui.state(id, () => ({ cursor: 0, offset: 0 })); + const drawTxt = txt.substr(state.offset, maxLen); + const key = hash([x, y, w, h]); + gui.registerID(id, key); + const box = gui.resource(id, key, () => rect([x, y], [w, h], {})); + const hover = isHoverSlider(gui, id, box, "text"); + const draw = gui.draw; + if (hover) { + if (gui.isMouseDown()) { + gui.activeID = id; + state.cursor = Math.min( + Math.round( + fitClamped( + gui.mouse[0], + x + pad, + x + w - pad, + state.offset, + state.offset + maxLen + ) + ), + txtLen + ); + } + info && draw && tooltipRaw(gui, info); + } + const focused = gui.requestFocus(id); + if (draw) { + box.attribs.fill = gui.bgColor(focused || hover); + box.attribs.stroke = gui.focusColor(id); + gui.add( + box, + textLabelRaw( + [x + pad, y + h / 2 + theme.baseLine], + gui.textColor(focused), + drawTxt + ) + ); + } + if (focused) { + const { cursor, offset } = state; + const drawCursor = Math.min(cursor - offset, maxLen); + if (draw) { + const xx = x + pad + drawCursor * cw; + (gui.time * theme.cursorBlink) % 1 < 0.5 && + gui.add([ + "line", + { stroke: theme.cursor }, + [xx, y + 4], + [xx, y + h - 4] + ]); + } + const k = gui.key; + switch (k) { + case "": + break; + case Key.TAB: + gui.switchFocus(); + break; + case Key.ENTER: + return txt; + case Key.BACKSPACE: + if (cursor > 0) { + const next = gui.isAltDown() + ? prevNonAlpha(txt, cursor - 1) + : cursor - 1; + move( + state, + next, + next - cursor, + drawCursor, + offset, + maxLen, + maxOffset + ); + return txt.substr(0, next) + txt.substr(cursor); + } + break; + case Key.DELETE: + if (cursor < txtLen) { + const next = gui.isAltDown() + ? nextNonAlpha(txt, cursor + 1) + : cursor + 1; + return txt.substr(0, cursor) + txt.substr(next + 1); + } + break; + case Key.LEFT: + if (cursor > 0) { + const next = gui.isAltDown() + ? prevNonAlpha(txt, cursor - 1) + : cursor - 1; + move( + state, + next, + next - cursor, + drawCursor, + offset, + maxLen, + maxOffset + ); + } + break; + case Key.RIGHT: + if (cursor < txtLen) { + const next = gui.isAltDown() + ? nextNonAlpha(txt, cursor + 1) + : cursor + 1; + move( + state, + next, + next - cursor, + drawCursor, + offset, + maxLen, + maxOffset + ); + } + break; + case Key.HOME: + move(state, 0, -cursor, drawCursor, offset, maxLen, maxOffset); + break; + case Key.END: + move( + state, + txtLen, + txtLen - cursor, + drawCursor, + offset, + maxLen, + maxOffset + ); + break; + default: { + if (k.length === 1 && filter(k)) { + move( + state, + cursor + 1, + 1, + drawCursor, + offset, + maxLen, + maxOffset + ); + return txt.substr(0, cursor) + k + txt.substr(cursor); + } + } + } + } + gui.lastID = id; +}; + +const WS = /\s/; + +const nextNonAlpha = (src: string, i: number) => { + const n = src.length; + while (i < n && WS.test(src[i])) i++; + for (; i < n && !WS.test(src[i]); i++) {} + return i; +}; + +const prevNonAlpha = (src: string, i: number) => { + while (i > 0 && WS.test(src[i])) i--; + for (; i > 0 && !WS.test(src[i]); i--) {} + return i; +}; + +const move = ( + state: TextfieldState, + next: number, + delta: number, + drawCursor: number, + offset: number, + maxLen: number, + maxOffset: number +) => { + state.cursor = next; + if (drawCursor + delta < 0) { + state.offset = Math.max(offset + delta, 0); + } else if (drawCursor + delta > maxLen) { + state.offset = Math.min(offset + delta, maxOffset); + } +}; diff --git a/packages/imgui/src/components/textlabel.ts b/packages/imgui/src/components/textlabel.ts new file mode 100644 index 0000000000..6298f43111 --- /dev/null +++ b/packages/imgui/src/components/textlabel.ts @@ -0,0 +1,49 @@ +import { isPlainObject } from "@thi.ng/checks"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { + Color, + GUITheme, + IGridLayout, + LayoutBox +} from "../api"; +import { IMGUI } from "../gui"; +import { isLayout } from "../layout"; + +export const textLabel = ( + gui: IMGUI, + layout: IGridLayout | LayoutBox, + label: string, + pad = false +) => { + const theme = gui.theme; + const { x, y, h } = isLayout(layout) ? layout.next() : layout; + gui.draw && + gui.add([ + "text", + { fill: gui.textColor(false) }, + [x + (pad ? theme.pad : 0), y + h / 2 + theme.baseLine], + label + ]); +}; + +export const textLabelRaw = ( + p: ReadonlyVec, + attribs: Color | any, + label: string +) => ["text", isPlainObject(attribs) ? attribs : { fill: attribs }, p, label]; + +export const textTransformH = ( + theme: GUITheme, + x: number, + y: number, + _: number, + h: number +) => [1, 0, 0, 1, x + theme.pad, y + h / 2 + theme.baseLine]; + +export const textTransformV = ( + theme: GUITheme, + x: number, + y: number, + w: number, + h: number +) => [0, -1, 1, 0, x + w / 2 + theme.baseLine, y + h - theme.pad]; diff --git a/packages/imgui/src/components/toggle.ts b/packages/imgui/src/components/toggle.ts new file mode 100644 index 0000000000..e87e46de4c --- /dev/null +++ b/packages/imgui/src/components/toggle.ts @@ -0,0 +1,91 @@ +import { rect } from "@thi.ng/geom"; +import { hash } from "@thi.ng/vectors"; +import { IGridLayout, LayoutBox } from "../api"; +import { handleButtonKeys, isHoverButton } from "../behaviors/button"; +import { IMGUI } from "../gui"; +import { isLayout } from "../layout"; +import { textLabelRaw } from "./textlabel"; +import { tooltipRaw } from "./tooltip"; + +/** + * If `square` is true, the clickable area will not fill the entire + * cell, but only a left-aligned square of cell/row height. + * + * @param gui + * @param layout + * @param id + * @param val + * @param i + * @param square + * @param label + * @param info + */ +export const toggle = ( + gui: IMGUI, + layout: IGridLayout | LayoutBox, + id: string, + val: boolean, + square?: boolean, + label?: string, + info?: string +) => { + const { x, y, w, h } = isLayout(layout) ? layout.next() : layout; + return toggleRaw( + gui, + id, + x, + y, + square ? h : w, + h, + square ? h : 0, + val, + label, + info + ); +}; + +export const toggleRaw = ( + gui: IMGUI, + id: string, + x: number, + y: number, + w: number, + h: number, + lx: number, + val: boolean, + label?: string, + info?: string +) => { + const theme = gui.theme; + const key = hash([x, y, w, h]); + gui.registerID(id, key); + let res: boolean | undefined; + const box = gui.resource(id, key, () => rect([x, y], [w, h])); + const hover = isHoverButton(gui, id, box); + const draw = gui.draw; + if (hover) { + gui.isMouseDown() && (gui.activeID = id); + info && draw && tooltipRaw(gui, info); + } + const focused = gui.requestFocus(id); + let changed = !gui.buttons && gui.hotID === id && gui.activeID === id; + focused && (changed = handleButtonKeys(gui) || changed); + changed && (res = val = !val); + if (draw) { + box.attribs = { + fill: val ? gui.fgColor(hover) : gui.bgColor(hover), + stroke: gui.focusColor(id) + }; + gui.add(box); + label && + gui.add( + textLabelRaw( + [x + theme.pad + lx, y + h / 2 + theme.baseLine], + gui.textColor(hover && lx > 0 && lx < w - theme.pad), + label + ) + ); + } + gui.lastID = id; + return res; +}; diff --git a/packages/imgui/src/components/tooltip.ts b/packages/imgui/src/components/tooltip.ts new file mode 100644 index 0000000000..592e392edd --- /dev/null +++ b/packages/imgui/src/components/tooltip.ts @@ -0,0 +1,19 @@ +import { rect } from "@thi.ng/geom"; +import { add2 } from "@thi.ng/vectors"; +import { IMGUI } from "../gui"; +import { textLabelRaw } from "./textlabel"; + +export const tooltipRaw = (gui: IMGUI, tooltip: string) => { + const theme = gui.theme; + const p = add2(null, [0, 10], gui.mouse); + gui.addOverlay( + rect(p, [tooltip.length * theme.charWidth + theme.pad, 20], { + fill: theme.bgTooltip + }), + textLabelRaw( + add2(null, [4, 10 + theme.baseLine], p), + theme.textTooltip, + tooltip + ) + ); +}; diff --git a/packages/imgui/src/components/xypad.ts b/packages/imgui/src/components/xypad.ts new file mode 100644 index 0000000000..9276758637 --- /dev/null +++ b/packages/imgui/src/components/xypad.ts @@ -0,0 +1,147 @@ +import { Fn } from "@thi.ng/api"; +import { line, rect } from "@thi.ng/geom"; +import { fit2, hash, Vec } from "@thi.ng/vectors"; +import { IGridLayout, LayoutBox } from "../api"; +import { handleSlider2Keys, isHoverSlider, slider2Val } from "../behaviors/slider"; +import { IMGUI } from "../gui"; +import { textLabelRaw } from "./textlabel"; +import { tooltipRaw } from "./tooltip"; + +/** + * `mode` interpretation: + * + * - -2 = square + * - -1 = proportional height (snapped to rows) + * - >0 = fixed row height + * + * @param gui + * @param layout + * @param id + * @param min + * @param max + * @param prec + * @param val + * @param mode + * @param yUp + * @param label + * @param fmt + * @param info + */ +export const xyPad = ( + gui: IMGUI, + layout: IGridLayout, + id: string, + min: Vec, + max: Vec, + prec: number, + val: Vec, + mode: number, + yUp: boolean, + label?: string, + fmt?: Fn, + info?: string +) => { + let box: LayoutBox; + const ch = layout.cellH; + const gap = layout.gap; + if (mode == -2) { + box = layout.nextSquare(); + } else { + let rows = (mode > 0 ? mode : layout.cellW / (ch + gap)) | 0; + box = layout.next([1, rows + 1]); + box.h -= ch + gap; + } + return xyPadRaw( + gui, + id, + box.x, + box.y, + box.w, + box.h, + min, + max, + prec, + val, + yUp, + 0, + box.h + gap + ch / 2 + gui.theme.baseLine, + label, + fmt, + info + ); +}; + +export const xyPadRaw = ( + gui: IMGUI, + id: string, + x: number, + y: number, + w: number, + h: number, + min: Vec, + max: Vec, + prec: number, + val: Vec, + yUp = false, + lx: number, + ly: number, + label?: string, + fmt?: Fn, + info?: string +) => { + const maxX = x + w; + const maxY = y + h; + const pos = yUp ? [x, maxY] : [x, y]; + const maxPos = yUp ? [maxX, y] : [maxX, maxY]; + const key = hash([x, y, w, h]); + gui.registerID(id, key); + const box = gui.resource(id, key, () => rect([x, y], [w, h])); + const col = gui.textColor(false); + const hover = isHoverSlider(gui, id, box, "move"); + const draw = gui.draw; + let v: Vec | undefined = val; + let res: Vec | undefined; + if (hover) { + if (gui.isMouseDown()) { + gui.activeID = id; + res = v = slider2Val( + fit2([], gui.mouse, pos, maxPos, min, max), + min, + max, + prec + ); + } + info && draw && tooltipRaw(gui, info); + } + const focused = gui.requestFocus(id); + if (draw) { + box.attribs = { + fill: gui.bgColor(hover || focused), + stroke: gui.focusColor(id) + }; + const { 0: cx, 1: cy } = fit2([], v, min, max, pos, maxPos); + gui.add( + box, + line([x, cy], [maxX, cy], { + stroke: col + }), + line([cx, y], [cx, maxY], { + stroke: col + }), + textLabelRaw( + [x + lx, y + ly], + col, + (label ? label + " " : "") + + (fmt ? fmt(val) : `${val[0] | 0}, ${val[1] | 0}`) + ) + ); + } + if ( + focused && + (v = handleSlider2Keys(gui, min, max, prec, v, yUp)) !== undefined + ) { + return v; + } + gui.lastID = id; + return res; +}; diff --git a/packages/imgui/src/events.ts b/packages/imgui/src/events.ts new file mode 100644 index 0000000000..40b567492a --- /dev/null +++ b/packages/imgui/src/events.ts @@ -0,0 +1,51 @@ +import { MouseButton } from "./api"; +import { IMGUI } from "./gui"; + +/** + * Injects default mouse & touch event handlers into `gui.attribs` and + * attaches keydown/up listeners to `window`. + * + * This method should only be used if the IMGUI is to be updated via a + * RAF loop or other non-reactive situation. For on-demand updates / + * rendering event handling and IMGUI mouse/key state preparation is + * left to the user. + * + * @see IMGUI.setMouse() + * @see IMGUI.setKey() + */ +export const useDefaultEventHandlers = (gui: IMGUI) => { + const pos = (e: MouseEvent | TouchEvent) => { + const b = (e.target).getBoundingClientRect(); + const t = (e).changedTouches + ? (e).changedTouches[0] + : e; + return [t.clientX - b.left, t.clientY - b.top]; + }; + const touchActive = (e: TouchEvent) => { + gui.setMouse(pos(e), MouseButton.LEFT); + }; + const touchEnd = (e: TouchEvent) => { + gui.setMouse(pos(e), 0); + }; + const mouseActive = (e: MouseEvent) => { + gui.setMouse(pos(e), e.buttons); + }; + Object.assign(gui.attribs, { + onmousemove: mouseActive, + onmousedown: mouseActive, + onmouseup: mouseActive, + ontouchstart: touchActive, + ontouchmove: touchActive, + ontouchend: touchEnd, + ontouchcancel: touchEnd + }); + window.addEventListener("keydown", (e) => { + gui.setKey(e); + if (e.key === "Tab") { + e.preventDefault(); + } + }); + window.addEventListener("keyup", (e) => { + gui.setKey(e); + }); +}; diff --git a/packages/imgui/src/gui.ts b/packages/imgui/src/gui.ts new file mode 100644 index 0000000000..8a1bcb07a1 --- /dev/null +++ b/packages/imgui/src/gui.ts @@ -0,0 +1,463 @@ +import { Fn0, IToHiccup } from "@thi.ng/api"; +import { set2, Vec } from "@thi.ng/vectors"; +import { + DEFAULT_THEME, + GUITheme, + Hash, + IMGUIOpts, + Key, + KeyModifier, + MouseButton, + NONE +} from "./api"; + +export class IMGUI implements IToHiccup { + attribs!: any; + layers: any[]; + + mouse: Vec; + buttons: number; + key!: string; + modifiers: number; + prevMouse: Vec; + prevButtons: number; + prevKey!: string; + prevModifiers: number; + + hotID: string; + activeID: string; + focusID: string; + lastID: string; + cursor!: string; + + t0: number; + time!: number; + + draw: boolean; + + protected currIDs: Set; + protected prevIDs: Set; + + protected themeStack!: GUITheme[]; + protected disabledStack!: boolean[]; + protected resources: Map>; + protected states: Map; + protected sizes: Map; + + constructor(opts: IMGUIOpts) { + this.mouse = [-1e3, -1e3]; + this.prevMouse = [-1e3, -1e3]; + this.key = this.prevKey = ""; + this.buttons = this.prevButtons = this.modifiers = this.prevModifiers = 0; + this.hotID = this.activeID = this.focusID = this.lastID = ""; + this.currIDs = new Set(); + this.prevIDs = new Set(); + this.resources = new Map>(); + this.sizes = new Map(); + this.states = new Map(); + this.layers = [[], []]; + this.attribs = {}; + this.disabledStack = [false]; + this.setTheme(opts.theme || {}); + this.draw = true; + this.t0 = Date.now(); + } + + get theme() { + const stack = this.themeStack; + return stack[stack.length - 1]; + } + + get disabled() { + const stack = this.disabledStack; + return stack[stack.length - 1]; + } + + /** + * Clears all shape layers and resets theme / disabled stacks. + */ + clear() { + this.layers[0].length = 0; + this.layers[1].length = 0; + this.themeStack.length = 1; + this.disabledStack.length = 1; + } + + /** + * Sets mouse position and current mouse button flags (i.e. + * `MouseEvent.buttons`). + * + * @param p + * @param buttons + */ + setMouse(p: Vec, buttons: number) { + set2(this.prevMouse, this.mouse); + set2(this.mouse, p); + this.prevButtons = this.buttons; + this.buttons = buttons; + return this; + } + + /** + * Sets internal key state from given key event details. + * + * @param e + */ + setKey(e: KeyboardEvent) { + if (e.type === "keydown") { + this.prevKey = this.key; + this.key = e.key; + } + this.prevModifiers = this.modifiers; + this.modifiers = + (~~e.shiftKey * KeyModifier.SHIFT) | + (~~e.ctrlKey * KeyModifier.CONTROL) | + (~~e.metaKey * KeyModifier.META) | + (~~e.altKey * KeyModifier.ALT); + return this; + } + + /** + * Merges given theme settings with `DEFAULT_THEME` and resets theme + * stack. + * + * @param theme + */ + setTheme(theme: Partial) { + this.themeStack = [{ ...DEFAULT_THEME, ...theme }]; + } + + /** + * Merges given theme settings with current theme and pushes result + * on theme stack. + * + * IMPORTANT: Currently IMGUI only supports one font and ignores any + * font changes pushed on the theme stack. + * + * @param theme + */ + beginTheme(theme: Partial) { + const stack = this.themeStack; + stack.push({ ...stack[stack.length - 1], ...theme }); + } + + /** + * Removes current theme from stack (unless only one theme left). + */ + endTheme() { + const stack = this.themeStack; + stack.length > 1 && stack.pop(); + } + + /** + * Applies component function with given theme, then restores + * previous theme and returns component result. + * + * @param theme + * @param comp + */ + withTheme(theme: Partial, comp: Fn0) { + this.beginTheme(theme); + const res = comp(); + this.themeStack.pop(); + return res; + } + + /** + * Pushes given disabled component state flag on stack (default: + * true, i.e. disabled). Pass `false` to temporarily enable + * components. + * + * @param disabled + */ + beginDisabled(disabled = true) { + this.disabledStack.push(disabled); + } + + /** + * Removes current disabled flag from stack (unless only one theme left). + */ + endDisabled() { + const stack = this.disabledStack; + stack.length > 1 && stack.pop(); + } + + /** + * Applies component function with given disabled flag, then + * restores previous disabled state and returns component result. + * + * @param disabled + * @param comp + */ + withDisabled(disabled: boolean, comp: Fn0) { + this.disabledStack.push(disabled); + const res = comp(); + this.disabledStack.pop(); + return res; + } + + /** + * Sets `focusID` to given `id` if the component can receive focus. + * Returns true if component is focused. + * + * @param id + */ + requestFocus(id: string) { + if (this.disabled) return false; + if (this.focusID === "" || this.activeID === id) { + this.focusID = id; + return true; + } + return this.focusID === id; + } + + /** + * Attempts to switch focus to next, or if Shift is pressed, to + * previous component. This is meant be called ONLY from component + * key handlers. + */ + switchFocus() { + this.focusID = this.isShiftDown() ? this.lastID : ""; + this.key = ""; + } + + /** + * Returns true if left mouse button is pressed. + */ + isMouseDown() { + return (this.buttons & MouseButton.LEFT) > 0; + } + + isShiftDown() { + return (this.modifiers & KeyModifier.SHIFT) > 0; + } + + isControlDown() { + return (this.modifiers & KeyModifier.CONTROL) > 0; + } + + isMetaDown() { + return (this.modifiers & KeyModifier.META) > 0; + } + + isAltDown() { + return (this.modifiers & KeyModifier.ALT) > 0; + } + + isPrevMouseDown() { + return (this.prevButtons & MouseButton.LEFT) > 0; + } + + isPrevShiftDown() { + return (this.prevModifiers & KeyModifier.SHIFT) > 0; + } + + isPrevControlDown() { + return (this.prevModifiers & KeyModifier.CONTROL) > 0; + } + + isPrevMetaDown() { + return (this.prevModifiers & KeyModifier.META) > 0; + } + + isPrevAltDown() { + return (this.prevModifiers & KeyModifier.ALT) > 0; + } + + /** + * Prepares IMGUI for next frame: + * + * - Resets `hotID`, `cursor` + * - Resets theme & disabled stacks + * - Clears all draw layers + * - Updates elapsed time. + * + * By default all components will emit draw shapes, however this can + * be disabled by passing `false` as argument. This is useful for + * use cases where the GUI is not updated at high frame rates and so + * would require two invocations per update cycle for immediate + * visual feedback: + * + * ``` + * gui.begin(false); // update state only, no draw + * updateMyGUI(); + * gui.end(); + * gui.begin(true); // run once more, with draw enabled (default) + * updateMyGUI(); + * gui.end(); + * ``` + * + * @param draw + */ + begin(draw = true) { + this.hotID = ""; + this.cursor = "default"; + this.draw = draw; + this.clear(); + this.time = (Date.now() - this.t0) * 1e-3; + } + + /** + * Performs end-of-frame handling & component cache cleanup. Also + * removes cached state and resources of all unused components. + */ + end() { + if (!this.buttons) { + this.activeID = ""; + } else { + if (this.activeID === "") { + this.activeID = NONE; + this.focusID = NONE; + this.lastID = ""; + } + } + this.key === Key.TAB && (this.focusID = ""); + this.key = ""; + // garbage collect unused component state / resources + const prev = this.prevIDs; + const curr = this.currIDs; + for (let id of prev) { + if (!curr.has(id)) { + this.resources.delete(id); + this.sizes.delete(id); + this.states.delete(id); + } + } + this.prevIDs = curr; + this.currIDs = prev; + prev.clear(); + } + + bgColor(hover: boolean) { + return this.disabled + ? this.theme.bgDisabled + : hover + ? this.theme.bgHover + : this.theme.bg; + } + + fgColor(hover: boolean) { + return this.disabled + ? this.theme.fgDisabled + : hover + ? this.theme.fgHover + : this.theme.fg; + } + + textColor(hover: boolean) { + return this.disabled + ? this.theme.textDisabled + : hover + ? this.theme.textHover + : this.theme.text; + } + + focusColor(id: string) { + return this.focusID === id ? this.theme.focus : undefined; + } + + /** + * Returns pixel width of given string based on current theme's font + * settings. + * + * IMPORTANT: Currently only monospace fonts are supported. + * + * @param txt + */ + textWidth(txt: string) { + return this.theme.charWidth * txt.length; + } + + /** + * Marks given component ID as used and checks `hash` to determine + * if the component's resource cache should be cleared. This hash + * value should be based on any values (e.g. layout info) which + * might invalidate cached resources. + * + * @param id + * @param hash + */ + registerID(id: string, hash: Hash) { + this.currIDs.add(id); + if (this.sizes.get(id) !== hash) { + // console.warn("cache miss:", id, hash); + this.sizes.set(id, hash); + this.resources.delete(id); + } + } + + /** + * Attempts to retrieve cached resource for given component `id` and + * resource `hash`. If unsuccessful, calls resource `ctor` function + * to create it, caches result and returns it. + * + * @see IMGUI.registerID() + * + * @param id + * @param hash + * @param ctor + */ + resource(id: string, hash: Hash, ctor: Fn0) { + let res: any; + let c = this.resources.get(id); + !c && this.resources.set(id, (c = new Map())); + return c.get(hash) || (c.set(hash, (res = ctor())), res); + } + + /** + * Attempts to retrieve cached component state for given `id`. If + * unsuccessful, calls state `ctor` function, caches result and + * returns it. + * + * @param id + * @param ctor + */ + state(id: string, ctor: Fn0): T { + let res: any = this.states.get(id); + return res !== undefined + ? res + : (this.states.set(id, (res = ctor())), res); + } + + /** + * Stores / overrides given local state value for component `id` in + * cache. + * + * @param id + * @param state + */ + setState(id: string, state: any) { + this.states.set(id, state); + } + + /** + * Sets cursor property to given `id`. This setting is cleared at + * the beginning of each frame (default value: "default"). + * + * @param id + */ + setCursor(id: string) { + this.cursor = id; + } + + add(...els: any[]) { + this.layers[0].push(...els); + } + + addOverlay(...els: any[]) { + this.layers[1].push(...els); + } + + /** + * Returns hiccup representation of all shapes/text primitives + * created by components in the current frame. + */ + toHiccup() { + return [ + "g", + { font: this.theme.font }, + ...this.layers[0], + ...this.layers[1] + ]; + } +} diff --git a/packages/imgui/src/hash.ts b/packages/imgui/src/hash.ts new file mode 100644 index 0000000000..0fd7b34932 --- /dev/null +++ b/packages/imgui/src/hash.ts @@ -0,0 +1,56 @@ +import { hash } from "@thi.ng/vectors"; + +const BUF = new Array(1024); + +/** + * Encodes given string into array of its char codes. If `buf` is not + * given, writes results into a shared, pre-defined array (use only for + * ephemeral purposes). + * + * @param txt + * @param buf + */ +export const encodeString = (txt: string, buf = BUF) => { + const n = (buf.length = txt.length); + for (let i = 0; i < n; i++) { + buf[i] = txt.charCodeAt(i); + } + return buf; +}; + +/** + * Returns Murmur3 hashcode for given string. + * + * @param txt + */ +export const hashString = (txt: string) => hash(encodeString(txt)); + +/** + * Mixes existing hash with that of given string. + * + * @param key + * @param txt + */ +export const mixHash = (key: number, txt: string) => key ^ hashString(txt); + +/** + * Hash helper for labels. Mixes existing hash with given label and + * GUI's disabled flag. + * + * @param key + * @param label + * @param disabled + */ +export const labelHash = (key: number, label: string, disabled: boolean) => + mixHash(key + ~~disabled, label); + +/** + * Hash helper for numeric value labels. Mixes existing hash with given + * value and GUI's disabled flag. + * + * @param key + * @param val + * @param disabled + */ +export const valHash = (key: number, val: number, disabled: boolean) => + mixHash(key + ~~disabled, String(val)); diff --git a/packages/imgui/src/index.ts b/packages/imgui/src/index.ts new file mode 100644 index 0000000000..617460890a --- /dev/null +++ b/packages/imgui/src/index.ts @@ -0,0 +1,23 @@ +export * from "./api"; +export * from "./events"; +export * from "./gui"; +export * from "./layout"; + +export * from "./components/button"; +export * from "./components/dial"; +export * from "./components/dropdown"; +export * from "./components/icon-button"; +export * from "./components/radial-menu"; +export * from "./components/radio"; +export * from "./components/ring"; +export * from "./components/sliderh"; +export * from "./components/sliderv"; +export * from "./components/textfield"; +export * from "./components/textlabel"; +export * from "./components/toggle"; +export * from "./components/tooltip"; +export * from "./components/xypad"; + +export * from "./behaviors/button"; +export * from "./behaviors/dial"; +export * from "./behaviors/slider"; diff --git a/packages/imgui/src/layout.ts b/packages/imgui/src/layout.ts new file mode 100644 index 0000000000..0751562a55 --- /dev/null +++ b/packages/imgui/src/layout.ts @@ -0,0 +1,148 @@ +import { implementsFunction } from "@thi.ng/checks"; +import { isNumber } from "@thi.ng/checks"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { IGridLayout, ILayout, LayoutBox } from "./api"; + +const DEFAULT_SPANS: [number, number] = [1, 1]; + +export class GridLayout implements IGridLayout { + readonly parent: GridLayout | null; + readonly cols: number; + readonly width: number; + readonly x: number; + readonly y: number; + readonly cellW: number; + readonly cellH: number; + readonly cellWG: number; + readonly cellHG: number; + readonly gap: number; + + protected currCol: number; + protected currRow: number; + protected rows: number; + + constructor( + parent: GridLayout | null, + x: number, + y: number, + width: number, + cols: number, + rowH: number, + gap: number + ) { + this.parent = parent; + this.cols = cols; + this.x = x; + this.y = y; + this.width = width; + this.cellW = (width - (cols - 1) * gap) / cols; + this.cellH = rowH; + this.cellWG = this.cellW + gap; + this.cellHG = rowH + gap; + this.gap = gap; + this.currCol = 0; + this.currRow = 0; + this.rows = 0; + } + + colsForWidth(w: number) { + return Math.ceil(w / this.cellWG); + } + + rowsForHeight(h: number) { + return Math.ceil(h / this.cellHG); + } + + spansForSize(size: ReadonlyVec): [number, number]; + spansForSize(w: number, h: number): [number, number]; + spansForSize(w: ReadonlyVec | number, h?: number): [number, number] { + const [ww, hh] = isNumber(w) ? [w, h!] : w; + return [this.colsForWidth(ww), this.rowsForHeight(hh)]; + } + + next(spans = DEFAULT_SPANS) { + const { cellWG, cellHG, gap, cols } = this; + const cspan = Math.min(spans[0], cols); + const rspan = spans[1]; + if (this.currCol > 0) { + if (this.currCol + cspan > cols) { + this.currCol = 0; + this.currRow = this.rows; + } + } else { + this.currRow = this.rows; + } + const h = rspan * cellHG - gap; + const cell = { + x: this.x + this.currCol * cellWG, + y: this.y + this.currRow * cellHG, + w: cspan * cellWG - gap, + h, + cw: this.cellW, + ch: this.cellH, + gap + }; + this.propagateSize(rspan); + this.currCol = Math.min(this.currCol + cspan, cols) % cols; + return cell; + } + + nextSquare() { + const box = this.next([ + 1, + Math.ceil(this.cellW / (this.cellH + this.gap)) + 1 + ]); + box.h = box.w; + return box; + } + + nest(cols: number, spans?: [number, number]) { + const { x, y, w } = this.next(spans); + return new GridLayout(this, x, y, w, cols, this.cellH, this.gap); + } + + /** + * Updates max rows used in this layout and all of its parents. + * + * @param rspan + */ + protected propagateSize(rspan: number) { + let rows = this.rows; + this.rows = rows = Math.max(rows, this.currRow + rspan); + const parent = this.parent; + parent && parent.propagateSize(rows); + } +} + +/** + * Syntax sugar for `GridLayout` ctor. By default creates a + * single-column layout at given position and width. + * + * @param x + * @param y + * @param width + * @param cols + * @param rowH + * @param gap + */ +export const gridLayout = ( + x: number, + y: number, + width: number, + cols = 1, + rowH = 16, + gap = 4 +) => new GridLayout(null, x, y, width, cols, rowH, gap); + +export const layoutBox = ( + x: number, + y: number, + w: number, + h: number, + cw: number, + ch: number, + gap: number +) => ({ x, y, w, h, cw, ch, gap }); + +export const isLayout = (x: any): x is ILayout => + implementsFunction(x, "next"); diff --git a/packages/arrays/test/index.ts b/packages/imgui/test/index.ts similarity index 50% rename from packages/arrays/test/index.ts rename to packages/imgui/test/index.ts index e74dbb435e..41d0cbf0ba 100644 --- a/packages/arrays/test/index.ts +++ b/packages/imgui/test/index.ts @@ -1,6 +1,6 @@ // import * as assert from "assert"; -// import * as a from "../src/index"; +// import * as i from "../src/index"; -describe("arrays", () => { +describe("imgui", () => { it("tests pending"); }); diff --git a/packages/imgui/test/tsconfig.json b/packages/imgui/test/tsconfig.json new file mode 100644 index 0000000000..f6e63560dd --- /dev/null +++ b/packages/imgui/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build", + "module": "commonjs" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/imgui/tsconfig.json b/packages/imgui/tsconfig.json new file mode 100644 index 0000000000..893b9979c5 --- /dev/null +++ b/packages/imgui/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "module": "es6", + "target": "es6" + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/interceptors/CHANGELOG.md b/packages/interceptors/CHANGELOG.md index e0a10f2311..0148c90cfe 100644 --- a/packages/interceptors/CHANGELOG.md +++ b/packages/interceptors/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@2.2.1...@thi.ng/interceptors@2.2.2) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/interceptors + + + + + +## [2.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@2.2.0...@thi.ng/interceptors@2.2.1) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/interceptors + + + + + +# [2.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@2.1.3...@thi.ng/interceptors@2.2.0) (2019-08-21) + + +### Features + +* **interceptors:** add module logger, setLogger() ([17f050d](https://github.com/thi-ng/umbrella/commit/17f050d)) + + + + + +## [2.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@2.1.2...@thi.ng/interceptors@2.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/interceptors + + + + + ## [2.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@2.1.1...@thi.ng/interceptors@2.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/interceptors diff --git a/packages/interceptors/README.md b/packages/interceptors/README.md index de78f32687..f712cac4bd 100644 --- a/packages/interceptors/README.md +++ b/packages/interceptors/README.md @@ -31,9 +31,42 @@ yarn add @thi.ng/interceptors import * as interceptors from "@thi.ng/interceptors"; ``` -### Event bus, interceptors, side effects +## Event bus, interceptors, side effects -Description forthcoming. Please check the detailed commented source code + +### [Interceptors](https://github.com/thi-ng/umbrella/blob/master/packages/interceptors/src/interceptors.ts) (Event and Effect primitives) + +The idea of interceptors is quite similar to functional composition and AOP ([aspect oriented programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming)). You want to reuse some functionality across components within your app. For example, if you have multiple actions which should be undoable, you can compose your main event handlers with the [`snapShot()`](https://github.com/thi-ng/umbrella/blob/master/packages/interceptors/src/interceptors.ts#L55) interceptor, which requires a [@thi.ng/atom](https://github.com/thi-ng/umbrella/tree/master/packages/atom)/History-like instance and records a snapshot of the current app state, but else is completely invisible. + +``` +[UNDOABLE_EVENT]: [snapshot(), valueSetter("foo")] +``` + +### Event Handlers + +The idea of **event** handlers is being responsible to assign parameters to side effects, rather than executing effects *themselves*, is again mainly to do with the DRY-principle, instrumentation potential and performance. Most composed event handler chains are setup so that your "actual" main handler is last in line in the pre processing phase. If e.g. your event handlers would directly update the state atom, then any attached watches [(derived views, cursors, other subscriptions)](https://github.com/thi-ng/umbrella/tree/master/packages/atom#about) would be re-run each time. By assigning the updated state to, e.g., an `FX_STATE` event, we can avoid these interim updates and only apply the new state once all events in the current frame have been processed. Furthermore, a post interceptor might cancel the event due to validation errors etc. + +#### Events vs Effects: + +To briefly summarize the differences between event handlers & effects: + +Event handlers are triggered by events, but each event handler is technically a chain of interceptors (even though many are just a single item). Even if you just specify a single function, it's internally translated into an array of interceptor objects like: + +``` +valueSetter("route") -> [{ pre: (...) => {[FX_STATE]: ...}, post: undefined }] +``` + +When processing an event, these interceptors are then executed first in ascending order for any pre functions and then backwards again for any post functions (only if there are any in the chain). So if you had defined an handler with this chain: `[{pre: f1, post: f2}, {pre: f3}, {pre: f4, post: f5}]`, then the functions would be called in this order: f1, f3, f4, f5, f2. The post phase is largely intended for state/effect validation & logging post-update. I.e., interceptors commonly need `pre` only. + +Like with [`trace()`](https://github.com/thi-ng/umbrella/blob/master/packages/interceptors/src/interceptors.ts#L21) some interceptors DO have side effects, but they're really the exception to the rule. For example, `snapshot()` is idempotent since it only records a new snapshot if it's different from the last and `trace()`, but is typically used during development only - its side effect is outside the scope of your app (i.e. the console). + +![but why](http://www.reactiongifs.com/r/but-why.gif) + +### Great, but why? + +In most apps there're far more event types/handlers than possible actions any component can take. So assigning them to registered side effects enables better code reuse. Another use-case is debugging. With a break point set at the beginning of `processEffects()` (in [`event-bus.ts`](https://github.com/thi-ng/umbrella/blob/master/packages/interceptors/src/event-bus.ts#L36)) you can see exactly which side effects have occured at each frame. This can be very helpful for debugging and avoid having to "keep everything in your head" or - as Rich Hickey would say - make your app "Easier to reason about". + +More comprehensive description forthcoming. Please check the detailed commented source code and examples for now: - [/src/event-bus.ts](https://github.com/thi-ng/umbrella/tree/master/packages/interceptors/src/event-bus.ts) @@ -59,4 +92,6 @@ Advanced: ## License + + © 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/interceptors/package.json b/packages/interceptors/package.json index e5f74ef7f1..73480f5c85 100644 --- a/packages/interceptors/package.json +++ b/packages/interceptors/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/interceptors", - "version": "2.1.2", + "version": "2.2.2", "description": "Interceptor based event bus, side effect & immutable state handling", "module": "./index.js", "main": "./lib/index.js", @@ -29,15 +29,15 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/atom": "^3.0.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/paths": "^2.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/atom": "^3.1.1", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/paths": "^2.1.6" }, "keywords": [ "ES6", diff --git a/packages/interceptors/src/api.ts b/packages/interceptors/src/api.ts index f133e69bdd..7fa274869b 100644 --- a/packages/interceptors/src/api.ts +++ b/packages/interceptors/src/api.ts @@ -1,3 +1,4 @@ +import { ILogger, NULL_LOGGER } from "@thi.ng/api"; import { ReadonlyAtom } from "@thi.ng/atom"; export type InterceptorFn = ( @@ -81,3 +82,7 @@ export interface InterceptorContext { [FX_DISPATCH_ASYNC]?: AsyncEffectDef | AsyncEffectDef[]; [id: string]: any; } + +export let LOGGER = NULL_LOGGER; + +export const setLogger = (logger: ILogger) => (LOGGER = logger); diff --git a/packages/interceptors/src/event-bus.ts b/packages/interceptors/src/event-bus.ts index 1dcc6bf35d..32c644cd8b 100644 --- a/packages/interceptors/src/event-bus.ts +++ b/packages/interceptors/src/event-bus.ts @@ -29,6 +29,7 @@ import { Interceptor, InterceptorContext, InterceptorFn, + LOGGER, SideEffect } from "./api"; @@ -195,10 +196,10 @@ export class StatelessEventBus implements IDispatch { this.dispatch([success, res]) ).catch((e) => this.dispatch([err, e])); } else { - console.warn("async effect did not return Promise"); + LOGGER.warn("async effect did not return Promise"); } } else { - console.warn(`skipping invalid async effect: ${id}`); + LOGGER.warn(`skipping invalid async effect: ${id}`); } }, -999 @@ -224,15 +225,11 @@ export class StatelessEventBus implements IDispatch { } addHandler(id: string, spec: EventDef) { - const iceps = isArray(spec) - ? (spec).map(asInterceptor) - : isFunction(spec) - ? [{ pre: spec }] - : [spec]; + const iceps = this.interceptorsFromSpec(spec); if (iceps.length > 0) { if (this.handlers[id]) { this.removeHandler(id); - console.warn(`overriding handler for ID: ${id}`); + LOGGER.warn(`overriding handler for ID: ${id}`); } this.handlers[id] = iceps; } else { @@ -249,7 +246,7 @@ export class StatelessEventBus implements IDispatch { addEffect(id: string, fx: SideEffect, priority = 1) { if (this.effects[id]) { this.removeEffect(id); - console.warn(`overriding effect for ID: ${id}`); + LOGGER.warn(`overriding effect for ID: ${id}`); } this.effects[id] = fx; const p: EffectPriority = [id, priority]; @@ -425,22 +422,37 @@ export class StatelessEventBus implements IDispatch { protected processEvent(ctx: InterceptorContext, e: Event) { const iceps = this.handlers[e[0]]; if (!iceps) { - console.warn(`missing handler for event type: ${e[0].toString()}`); + LOGGER.warn(`missing handler for event type: ${e[0].toString()}`); return; } - const n = iceps.length - 1; + if (!this.processForward(ctx, iceps, e)) { + return; + } + this.processReverse(ctx, iceps, e); + } + + protected processForward( + ctx: InterceptorContext, + iceps: Interceptor[], + e: Event + ) { let hasPost = false; - for (let i = 0; i <= n && !ctx[FX_CANCEL]; i++) { + for (let i = 0, n = iceps.length; i < n && !ctx[FX_CANCEL]; i++) { const icep = iceps[i]; if (icep.pre) { this.mergeEffects(ctx, icep.pre(ctx[FX_STATE], e, this, ctx)); } hasPost = hasPost || !!icep.post; } - if (!hasPost) { - return; - } - for (let i = n; i >= 0 && !ctx[FX_CANCEL]; i--) { + return hasPost; + } + + protected processReverse( + ctx: InterceptorContext, + iceps: Interceptor[], + e: Event + ) { + for (let i = iceps.length; --i >= 0 && !ctx[FX_CANCEL]; ) { const icep = iceps[i]; if (icep.post) { this.mergeEffects(ctx, icep.post(ctx[FX_STATE], e, this, ctx)); @@ -459,16 +471,23 @@ export class StatelessEventBus implements IDispatch { for (let p of this.priorities) { const id = p[0]; const val = ctx[id]; - if (val !== undefined) { - const fn = effects[id]; - if (id !== FX_STATE) { - for (let v of val) { - fn(v, this, ctx); - } - } else { - fn(val, this, ctx); - } + val !== undefined && this.processEffect(ctx, effects, id, val); + } + } + + protected processEffect( + ctx: InterceptorContext, + effects: IObjectOf, + id: string, + val: any + ) { + const fn = effects[id]; + if (id !== FX_STATE) { + for (let v of val) { + fn(v, this, ctx); } + } else { + fn(val, this, ctx); } } @@ -543,6 +562,14 @@ export class StatelessEventBus implements IDispatch { } } } + + protected interceptorsFromSpec(spec: EventDef) { + return isArray(spec) + ? (spec).map(asInterceptor) + : isFunction(spec) + ? [{ pre: spec }] + : [spec]; + } } /** @@ -726,7 +753,7 @@ const undoHandler = (action: string): InterceptorFn => ( bus, ctx ) => { - let id = ev ? ev[0] : "history"; + const id = ev ? ev[0] : "history"; if (implementsFunction(ctx[id], action)) { const ok = ctx[id][action](); return { @@ -738,6 +765,6 @@ const undoHandler = (action: string): InterceptorFn => ( : undefined }; } else { - console.warn("no history in context"); + LOGGER.warn("no history in context"); } }; diff --git a/packages/interceptors/src/interceptors.ts b/packages/interceptors/src/interceptors.ts index 098c31a47c..a2d56ef3ca 100644 --- a/packages/interceptors/src/interceptors.ts +++ b/packages/interceptors/src/interceptors.ts @@ -135,6 +135,12 @@ export const ensurePred = ( } : undefined; +const eventPathState = ( + state: any, + path: Fn | undefined, + e: Event +) => getIn(state, path ? path(e) : e[1]); + /** * Specialization of `ensurePred()` to ensure a state value is less than * given max at the time when the event is being processed. The optional @@ -158,7 +164,7 @@ export const ensureStateLessThan = ( max: number, path?: Fn, err?: InterceptorFn -) => ensurePred((state, e) => getIn(state, path ? path(e) : e[1]) < max, err); +) => ensurePred((state, e) => eventPathState(state, path, e) < max, err); /** * Specialization of `ensurePred()` to ensure a state value is greater @@ -172,7 +178,7 @@ export const ensureStateGreaterThan = ( min: number, path?: Fn, err?: InterceptorFn -) => ensurePred((state, e) => getIn(state, path ? path(e) : e[1]) > min, err); +) => ensurePred((state, e) => eventPathState(state, path, e) > min, err); /** * Specialization of `ensurePred()` to ensure a state value is within @@ -191,7 +197,7 @@ export const ensureStateRange = ( err?: InterceptorFn ) => ensurePred((state, e) => { - const x = getIn(state, path ? path(e) : e[1]); + const x = eventPathState(state, path, e); return x >= min && x <= max; }, err); diff --git a/packages/intervals/CHANGELOG.md b/packages/intervals/CHANGELOG.md index 3ae4f891bc..375d26ae41 100644 --- a/packages/intervals/CHANGELOG.md +++ b/packages/intervals/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.15](https://github.com/thi-ng/umbrella/compare/@thi.ng/intervals@1.0.14...@thi.ng/intervals@1.0.15) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/intervals + + + + + +## [1.0.14](https://github.com/thi-ng/umbrella/compare/@thi.ng/intervals@1.0.13...@thi.ng/intervals@1.0.14) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/intervals + + + + + +## [1.0.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/intervals@1.0.12...@thi.ng/intervals@1.0.13) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/intervals + + + + + ## [1.0.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/intervals@1.0.11...@thi.ng/intervals@1.0.12) (2019-07-31) **Note:** Version bump only for package @thi.ng/intervals diff --git a/packages/intervals/package.json b/packages/intervals/package.json index d77d871d54..3d635e069e 100644 --- a/packages/intervals/package.json +++ b/packages/intervals/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/intervals", - "version": "1.0.12", + "version": "1.0.15", "description": "Closed/open/semi-open interval data type, queries & operations", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/errors": "^1.2.1" }, "keywords": [ "ES6", diff --git a/packages/intervals/src/index.ts b/packages/intervals/src/index.ts index 25632f54c8..d95b929ac6 100644 --- a/packages/intervals/src/index.ts +++ b/packages/intervals/src/index.ts @@ -1,4 +1,10 @@ -import { Fn, ICompare, IContains, ICopy, IEquiv } from "@thi.ng/api"; +import { + Fn, + ICompare, + IContains, + ICopy, + IEquiv +} from "@thi.ng/api"; import { illegalArgs } from "@thi.ng/errors"; export const enum Classifier { @@ -30,8 +36,8 @@ export class Interval * * Openness / closedness symbols: * - * - LHS: `]` or `(` (open), `[` (closed) - * - RHS: `[` or `)` (open), `]` (closed) + * - LHS: open: `]` / `(`, closed: `[` + * - RHS: open: `[` / `)`, closed: `]` * * ``` * // semi-open interval between -∞ and +1 @@ -70,8 +76,8 @@ export class Interval (x === "" && i === 0) || (inf && inf[1] === "-") ? -Infinity : (x === "" && i > 0) || (inf && inf[1] !== "-") - ? Infinity - : parseFloat(x); + ? Infinity + : parseFloat(x); if (isNaN(n)) { illegalArgs(`term: '${x}'`); } @@ -147,12 +153,12 @@ export class Interval return this.l < i.l ? -1 : this.l > i.l - ? 1 - : this.r < i.r - ? -1 - : this.r > i.r - ? 1 - : 0; + ? 1 + : this.r < i.r + ? -1 + : this.r > i.r + ? 1 + : 0; } equiv(i: any) { @@ -211,8 +217,8 @@ export class Interval return this.isBefore(x) ? new Interval(x, this.r, false, this.ropen) : this.isAfter(x) - ? new Interval(this.l, x, this.lopen, false) - : this; + ? new Interval(this.l, x, this.lopen, false) + : this; } /** @@ -225,8 +231,8 @@ export class Interval return this.overlaps(i) ? 0 : this.l < i.l - ? i.l - this.r - : this.l - i.r; + ? i.l - this.r + : this.l - i.r; } /** @@ -240,14 +246,14 @@ export class Interval return this.isBefore(i.r) ? Classifier.DISJOINT_RIGHT : this.isAfter(i.l) - ? Classifier.DISJOINT_LEFT - : this.contains(i.l) - ? this.contains(i.r) - ? Classifier.SUPERSET - : Classifier.OVERLAP_RIGHT - : this.contains(i.r) - ? Classifier.OVERLAP_LEFT - : Classifier.SUBSET; + ? Classifier.DISJOINT_LEFT + : this.contains(i.l) + ? this.contains(i.r) + ? Classifier.SUPERSET + : Classifier.OVERLAP_RIGHT + : this.contains(i.r) + ? Classifier.OVERLAP_LEFT + : Classifier.SUBSET; } overlaps(i: Readonly) { @@ -306,7 +312,7 @@ export class Interval ao: boolean, bo: boolean ): [number, boolean] { - return a < b ? [a, ao] : a === b ? [a, ao || bo] : [b, bo]; + return minmax(a < b, a, b, ao, bo); } protected $max( @@ -315,6 +321,14 @@ export class Interval ao: boolean, bo: boolean ): [number, boolean] { - return a > b ? [a, ao] : a === b ? [a, ao || bo] : [b, bo]; + return minmax(a > b, a, b, ao, bo); } } + +const minmax = ( + test: boolean, + a: number, + b: number, + ao: boolean, + bo: boolean +): [number, boolean] => (test ? [a, ao] : a === b ? [a, ao || bo] : [b, bo]); diff --git a/packages/iterators/CHANGELOG.md b/packages/iterators/CHANGELOG.md index 0237b17901..81ca75c82e 100644 --- a/packages/iterators/CHANGELOG.md +++ b/packages/iterators/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [5.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@5.1.5...@thi.ng/iterators@5.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/iterators + + + + + +## [5.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@5.1.4...@thi.ng/iterators@5.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/iterators + + + + + +## [5.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@5.1.3...@thi.ng/iterators@5.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/iterators + + + + + +## [5.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@5.1.2...@thi.ng/iterators@5.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/iterators + + + + + ## [5.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@5.1.1...@thi.ng/iterators@5.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/iterators diff --git a/packages/iterators/package.json b/packages/iterators/package.json index 5168348d9e..c5d76b191c 100644 --- a/packages/iterators/package.json +++ b/packages/iterators/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/iterators", - "version": "5.1.2", + "version": "5.1.6", "description": "clojure.core inspired, composable ES6 iterators & generators", "module": "./index.js", "main": "./lib/index.js", @@ -29,13 +29,13 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/dcons": "^2.1.2", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/dcons": "^2.1.6", + "@thi.ng/errors": "^1.2.1" }, "keywords": [ "clojure", diff --git a/packages/iterators/src/fork.ts b/packages/iterators/src/fork.ts index 7dc107daf4..e5a6df121c 100644 --- a/packages/iterators/src/fork.ts +++ b/packages/iterators/src/fork.ts @@ -5,7 +5,7 @@ export const fork = (src: Iterable, cacheLimit = 16) => { const iter = iterator(src); const cache = new DCons(); const forks: number[] = []; - let done = false; + let done: boolean | undefined = false; let total = 0; const consume = () => { diff --git a/packages/leb128/CHANGELOG.md b/packages/leb128/CHANGELOG.md index 9e43aa7338..6f3ea534e9 100644 --- a/packages/leb128/CHANGELOG.md +++ b/packages/leb128/CHANGELOG.md @@ -3,6 +3,46 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/leb128@0.1.5...@thi.ng/leb128@1.0.0) (2019-11-09) + + +### Features + +* **leb128:** no more async init, remove READY promise, update tests ([2044583](https://github.com/thi-ng/umbrella/commit/20445837f5af1891703e1c51fe8db56e69f11c86)) + + +### BREAKING CHANGES + +* **leb128:** switch to synchronous initialization + + + + + +## [0.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/leb128@0.1.4...@thi.ng/leb128@0.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/leb128 + + + + + +## [0.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/leb128@0.1.3...@thi.ng/leb128@0.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/leb128 + + + + + +## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/leb128@0.1.2...@thi.ng/leb128@0.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/leb128 + + + + + ## [0.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/leb128@0.1.1...@thi.ng/leb128@0.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/leb128 diff --git a/packages/leb128/README.md b/packages/leb128/README.md index ff36355d4b..4f8b4de5c7 100644 --- a/packages/leb128/README.md +++ b/packages/leb128/README.md @@ -57,23 +57,20 @@ yarn add @thi.ng/leb128 ```ts import * as leb from "@thi.ng/leb128"; -// since WASM initialization is async, need to wait until module is ready... // if WASM is unavailable, the encode/decode functions will throw an error -leb.READY.then(()=> { - // encode unsigned int (input val up to 64 bits) - enc = leb.encodeULEB128(Number.MAX_SAFE_INTEGER); - // Uint8Array [ 255, 255, 255, 255, 255, 255, 255, 15 ] - - // decoding returns tuple of [value, bytes consumed] - leb.decodeULEB128(enc); - // [ 9007199254740991, 8 ] - - // encode signed int - enc = leb.encodeSLEB128(Number.MIN_SAFE_INTEGER) - // Uint8Array [ 129, 128, 128, 128, 128, 128, 128, 112 ] - leb.decodeSLEB128(enc) - // [ -9007199254740991, 8 ] -}); +enc = leb.encodeULEB128(Number.MAX_SAFE_INTEGER); +// Uint8Array [ 255, 255, 255, 255, 255, 255, 255, 15 ] + +// decoding returns tuple of [value, bytes consumed] +leb.decodeULEB128(enc); +// [ 9007199254740991, 8 ] + +// encode signed int +enc = leb.encodeSLEB128(Number.MIN_SAFE_INTEGER) +// Uint8Array [ 129, 128, 128, 128, 128, 128, 128, 112 ] + +leb.decodeSLEB128(enc) +// [ -9007199254740991, 8 ] ``` ## Building the binary @@ -97,7 +94,7 @@ wasm-opt leb128.wasm -o opt.wasm -Os wasm2wat opt.wasm # base64 encode and generate src/binary.ts -echo "export const BINARY = \"$(base64 -i opt.wasm)\";" > src/binary.ts +yarn build:binary # test TS/JS version yarn test diff --git a/packages/leb128/package.json b/packages/leb128/package.json index 2355398db0..4c20645de3 100644 --- a/packages/leb128/package.json +++ b/packages/leb128/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/leb128", - "version": "0.1.2", + "version": "1.0.0", "description": "WASM based LEB128 encoder / decoder (signed & unsigned)", "module": "./index.js", "main": "./lib/index.js", @@ -18,6 +18,7 @@ "build:release": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module all", "build:es6": "tsc --declaration", "build:test": "rimraf build && tsc -p test/tsconfig.json", + "build:binary": "echo \"export const BINARY = \\\"$(../../scripts/base64 opt.wasm)\\\";\" > src/binary.ts", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib", @@ -29,13 +30,13 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/checks": "^2.2.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers-binary": "^0.4.2" + "@thi.ng/checks": "^2.4.1", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/transducers-binary": "^0.4.6" }, "keywords": [ "LEB128", diff --git a/packages/leb128/src/index.ts b/packages/leb128/src/index.ts index 26724d105a..881cbbafb8 100644 --- a/packages/leb128/src/index.ts +++ b/packages/leb128/src/index.ts @@ -15,22 +15,13 @@ interface LEB128 { let wasm: LEB128; let U8: Uint8Array; -/** - * Promise indicating that the WASM module has been initialized and is - * ready to use. A `false` result means WASM isn't available at all. - */ -export let READY: Promise; - if (hasWASM()) { - READY = WebAssembly.instantiate(new Uint8Array([...base64Decode(BINARY)])) - .then((inst) => { - wasm = inst.instance.exports; - // mapped view of the data buffer - U8 = new Uint8Array(wasm.memory.buffer, wasm.buf, 16); - }) - .then(() => true); -} else { - READY = Promise.resolve(false); + const inst = new WebAssembly.Instance( + new WebAssembly.Module(new Uint8Array([...base64Decode(BINARY)])) + ); + wasm = inst.exports; + // mapped view of the data buffer + U8 = new Uint8Array(wasm.memory.buffer, wasm.buf, 16); } const ensureWASM = () => !wasm && unsupported("WASM module unavailable"); diff --git a/packages/leb128/test/index.ts b/packages/leb128/test/index.ts index cd821cd836..52e9126833 100644 --- a/packages/leb128/test/index.ts +++ b/packages/leb128/test/index.ts @@ -4,15 +4,13 @@ import { decodeSLEB128, decodeULEB128, encodeSLEB128, - encodeULEB128, - READY + encodeULEB128 } from "../src/index"; describe("leb128", () => { if (hasWASM()) { - it("signed", async () => { + it("signed", () => { let a; - assert(await READY, "WASM not available"); assert.deepEqual( [...(a = encodeSLEB128(Number.MAX_SAFE_INTEGER))], [255, 255, 255, 255, 255, 255, 255, 15] @@ -27,9 +25,8 @@ describe("leb128", () => { assert.deepEqual(decodeSLEB128(encodeSLEB128(-64)), [-64, 1]); }); - it("unsigned", async () => { + it("unsigned", () => { let a; - assert(await READY, "WASM not available"); assert.deepEqual( [...(a = encodeULEB128(Number.MAX_SAFE_INTEGER))], [255, 255, 255, 255, 255, 255, 255, 15] diff --git a/packages/lsys/CHANGELOG.md b/packages/lsys/CHANGELOG.md index cf30726b99..8986be7263 100644 --- a/packages/lsys/CHANGELOG.md +++ b/packages/lsys/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/lsys@0.2.23...@thi.ng/lsys@0.2.24) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/lsys + + + + + +## [0.2.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/lsys@0.2.22...@thi.ng/lsys@0.2.23) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/lsys + + + + + +## [0.2.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/lsys@0.2.21...@thi.ng/lsys@0.2.22) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/lsys + + + + + +## [0.2.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/lsys@0.2.20...@thi.ng/lsys@0.2.21) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/lsys + + + + + +## [0.2.20](https://github.com/thi-ng/umbrella/compare/@thi.ng/lsys@0.2.19...@thi.ng/lsys@0.2.20) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/lsys + + + + + +## [0.2.19](https://github.com/thi-ng/umbrella/compare/@thi.ng/lsys@0.2.18...@thi.ng/lsys@0.2.19) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/lsys + + + + + ## [0.2.18](https://github.com/thi-ng/umbrella/compare/@thi.ng/lsys@0.2.17...@thi.ng/lsys@0.2.18) (2019-07-31) **Note:** Version bump only for package @thi.ng/lsys diff --git a/packages/lsys/README.md b/packages/lsys/README.md index aae9b7d9cb..1f86fd3912 100644 --- a/packages/lsys/README.md +++ b/packages/lsys/README.md @@ -60,10 +60,10 @@ yarn add @thi.ng/lsys ## Usage examples -| Examples | | -|----------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------| -| ![example](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/lsys-0.png) | ![example](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/lsys-1.png) | -| ![example](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/lsys-2.png) | ![example](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/lsys-3.png) | +| Examples | | +|---------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| +| ![example](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/lsys/lsys-0.png) | ![example](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/lsys/lsys-1.png) | +| ![example](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/lsys/lsys-2.png) | ![example](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/lsys/lsys-3.png) | ```ts import * as lsys from "@thi.ng/lsys"; @@ -109,7 +109,7 @@ stochastic features, e.g. randomization of growth direction and stochastic branch termination. This enables the creation of more organic looking structures, like shown in the following example: - ![stochastic L-system](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/lsys-tree.png) + ![stochastic L-system](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/lsys/lsys-tree.png) ```ts import { XsAdd } from "@thi.ng/random"; diff --git a/packages/lsys/package.json b/packages/lsys/package.json index a1e3844a2c..d7c411d07f 100644 --- a/packages/lsys/package.json +++ b/packages/lsys/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/lsys", - "version": "0.2.18", + "version": "0.2.24", "description": "Functional, extensible L-System architecture w/ support for probabilistic rules", "module": "./index.js", "main": "./lib/index.js", @@ -29,17 +29,17 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/compose": "^1.3.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/math": "^1.4.2", - "@thi.ng/random": "^1.1.10", - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/api": "^6.5.0", + "@thi.ng/compose": "^1.3.5", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/math": "^1.5.0", + "@thi.ng/random": "^1.1.13", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "axiom", diff --git a/packages/malloc/AUTHORS.md b/packages/malloc/AUTHORS.md new file mode 100644 index 0000000000..b6a4457eee --- /dev/null +++ b/packages/malloc/AUTHORS.md @@ -0,0 +1,7 @@ +## Maintainer + +- Karsten Schmidt (@postspectacular) + +## Contributors + +- Bnaya Peretz (@Bnaya) diff --git a/packages/malloc/CHANGELOG.md b/packages/malloc/CHANGELOG.md index 38be4f8274..5e336ad1d4 100644 --- a/packages/malloc/CHANGELOG.md +++ b/packages/malloc/CHANGELOG.md @@ -3,6 +3,48 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.1.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/malloc@4.0.5...@thi.ng/malloc@4.1.0) (2019-11-09) + + +### Bug Fixes + +* **malloc:** fix realloc(), various refactorings, add tests ([fa3e1bc](https://github.com/thi-ng/umbrella/commit/fa3e1bcff26f553d845d2145ed7c8f9238b796bd)) +* **malloc:** update freeAll(), add test, doc strings, minor cleanup ([830b267](https://github.com/thi-ng/umbrella/commit/830b267f8bf3f050ea5914b7e9f8ba539dcd0c4e)) + + +### Features + +* **malloc:** add more buffered state, align opt, refactor, update tests ([1ff9487](https://github.com/thi-ng/umbrella/commit/1ff9487980645315e77df02af651ff442288f1a9)) +* **malloc:** fix block alignment/layout, update calloc/realloc ([a40c265](https://github.com/thi-ng/umbrella/commit/a40c265708fc6e66bef5a700b436569106f81e31)) + + + + + +## [4.0.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/malloc@4.0.4...@thi.ng/malloc@4.0.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/malloc + + + + + +## [4.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/malloc@4.0.3...@thi.ng/malloc@4.0.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/malloc + + + + + +## [4.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/malloc@4.0.2...@thi.ng/malloc@4.0.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/malloc + + + + + ## [4.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/malloc@4.0.1...@thi.ng/malloc@4.0.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/malloc diff --git a/packages/malloc/README.md b/packages/malloc/README.md index 9b3f7e3c9d..47ec4e174c 100644 --- a/packages/malloc/README.md +++ b/packages/malloc/README.md @@ -10,6 +10,9 @@ This project is part of the - [About](#about) +- [Memory layout](#memory-layout) +- [Free block compaction / coalescing](#free-block-compaction--coalescing) +- [Block splitting](#block-splitting) - [Installation](#installation) - [Dependencies](#dependencies) - [Usage examples](#usage-examples) @@ -17,8 +20,8 @@ This project is part of the - [MemPool](#mempool) - [`malloc(size: number)`](#mallocsize-number) - [`mallocAs(type: Type, num: number)`](#mallocastype-type-num-number) - - [`calloc(size: number)`](#callocsize-number) - - [`callocAs(type: Type, num: number)`](#callocastype-type-num-number) + - [`calloc(size: number, fill = 0)`](#callocsize-number-fill--0) + - [`callocAs(type: Type, num: number, fill = 0)`](#callocastype-type-num-number-fill--0) - [`realloc(addr: number, size: number)`](#reallocaddr-number-size-number) - [`reallocArray(buf: TypedArray, num: number)`](#reallocarraybuf-typedarray-num-number) - [`free(addr: number | TypedArray)`](#freeaddr-number--typedarray) @@ -48,6 +51,74 @@ Even for non-WASM use cases, using this package can drastically speed up allocation of typed arrays and reduce GC pressure. See [benchmarks](#benchmarks) below. +## Memory layout + +Since v4.1.0, all internal allocator state is stored in the +`ArrayBuffer` itself (thanks to the [initial idea & work done by +@Bnaya](https://github.com/thi-ng/umbrella/pull/153)). This change +allows the `ArrayBuffer` (or `SharedArrayBuffer`) being passed to +workers and/or the entire memory state being easily serialized/restored +to/from local storage. + +The new memory layout is as follows: + +![Memory layout diagram](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/malloc/malloc-layout.png) + +## Free block compaction / coalescing + +The allocator supports coalescing of free memory blocks to minimize +fragmentation of the managed address space. This feature is enabled by +default, but can be disabled via config options. + +The following diagrams show the different stages of this behavior: + +**Initial layout** + +In this example we start with three allocated neighboring blocks: + +![Block compaction (initial layout)](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/malloc/compact-01.png) + +**Non-continuous free blocks** + +After freeing the first & last blocks, the free blocks are linked via +their `next` pointers, but still occupy non-continuous memory regions. + +![Block compaction (non-continuous)](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/malloc/compact-02.png) + +**Single compacted free block** + +After also freeing block #2, all three blocks now span a single +continuous address range and are merged into a single larger free block. +Furthermore, if that resulting new block turns out to be the top-most +block (in terms of previously allocated address space), the allocator +does not create a free block at all, but merely resets its heap `top` +pointer to the beginning of that block and considers it blank space that +way (essentially a merge with the remaining free/unallocated space of +the array buffer). + +![Block compaction (result)](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/malloc/compact-03.png) + +## Block splitting + +In order to avoid unnecessary growing of the heap `top`, the allocator +can split existing free blocks if the user requests allocating a smaller +size than that of a suitable free block available. If the free block has +sufficient excess space (configurable via the `minSplit` option), the +free block will be split, the lower part marked as allocated and +inserted into the used block list and the excess space inserted as new +free block back into the free list. + +This behavior too is enabled by default, but can be turned off via the +`split` config option. + +Initial example layout: + +![Block splitting (initial layout)](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/malloc/split-01.png) + +Layout after allocating only a smaller size than the free block's capacity: + +![Block splitting (result)](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/malloc/split-02.png) + ## Installation ```bash @@ -59,6 +130,7 @@ yarn add @thi.ng/malloc - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) - [@thi.ng/binary](https://github.com/thi-ng/umbrella/tree/master/packages/binary) - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/master/packages/checks) +- [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/master/packages/errors) ## Usage examples @@ -76,8 +148,10 @@ const pool = new MemPool({ size: 0x1000, start: 8 }); ptr = pool.malloc(16); // 8 -// request memory and return as typed view +// request memory and return as typed array view // in this case we use number of elements, NOT bytes +// IMPORTANT: there's no guarantee that the returned array's +// values are zeroed. Use `calloc`/`callocAs` for that purpose vec = pool.mallocAs(Type.F64, 4); // Float64Array [ 0, 0, 0, 0 ] @@ -121,40 +195,27 @@ pool.stats(); ### MemPool -The `MemPool` constructor takes an `ArrayBuffer` (or size only) and an -optional `MemPoolOpts` object for specifying start and end addresses -(byte offsets) delineating the allocatable / managed region and other -options. +The `MemPool` constructor takes an object of optional configuration +options. See +[`MemPoolOpts`](https://github.com/thi-ng/umbrella/blob/master/packages/malloc/src/api.ts#L9) +for further reference: ```ts -// example with default options shown -new MemPool(0x1000, { - start: 0x8, - end: 0x1000, +// example with some default options shown +new MemPool({ + size: 0x1000, compact: true, split: true, minSplit: 16 }); ``` -The default `start` address is 8 and `end` the length of the buffer. This -start address is also the minimum supported address for memory blocks. -Address 0x0 is reserved as return value for allocation errors. - -The `compact` option enables recursive compaction / joining of -neighboring free blocks. Enabled by default to minimize fragmentation. - -The `split` option is used to enable (default) splitting of a larger -suitable free block when allocating a smaller size. `minSplit` specifies -the minimum excess between requested size and actual block size, i.e. by -default a block will be split during allocation if there're at least 16 -bytes left over. The given value should always be a multiple of 8. - ### `malloc(size: number)` Attempts to allocate a new block of memory of given byte size and returns start address if successful, or zero (`0`) if unsuccessful. -Memory blocks always start at multiples of 8. +Memory blocks always start at multiples of the configured alignment +(default: 8). ### `mallocAs(type: Type, num: number)` @@ -168,15 +229,16 @@ package, e.g. `Type.F64`: `U8`, `U8C`, `I8`, `U16`, `I16`, `U32`, `I32`, `F32`, `F64` -### `calloc(size: number)` +### `calloc(size: number, fill = 0)` -Like `malloc()` but zeroes allocated block before returning. Unless the -allocated block is immediately filled with user data, this method is -preferred over `malloc()`. +Like `malloc()` but fill allocated block with given `fill` value before +returning. Unless the allocated block is immediately filled with user +data, this method is preferred over `malloc()` for safety. -### `callocAs(type: Type, num: number)` +### `callocAs(type: Type, num: number, fill = 0)` -Like `mallocAs()` but zeroes allocated block before returning. +Like `mallocAs()` but fills allocated block with `fill` value before +returning. ### `realloc(addr: number, size: number)` @@ -219,18 +281,29 @@ Returns pool statistics (see above example). ## Benchmarks +Benchmark +([source](https://github.com/thi-ng/umbrella/blob/master/packages/malloc/bench/index.js)) +comparing against raw typed array construction of different sizes: + ```bash node bench/index.js ``` ```text -1x f64x4 malloc x 8,712,284 ops/sec Β±0.39% (92 runs sampled) mean: 0.00011ms -1x f64x4 vanilla x 1,714,557 ops/sec Β±2.18% (82 runs sampled) mean: 0.00058ms +malloc_f64x4 x 3,317,667 ops/sec Β±0.61% (93 runs sampled) mean: 0.00030ms** +malloc_f64x4_vanilla x 4,221,089 ops/sec Β±0.32% (94 runs sampled) mean: 0.00024ms + +malloc6_f64 x 358,405 ops/sec Β±0.37% (90 runs sampled) mean: 0.00279ms +malloc6_f64_vanilla x 283,487 ops/sec Β±0.81% (90 runs sampled) mean: 0.00353ms -6x f64 malloc x 704,920 ops/sec Β±1.20% (91 runs sampled) mean: 0.00142ms -6x f64 vanilla x 251,799 ops/sec Β±1.87% (84 runs sampled) mean: 0.00397ms +malloc_f32x1024 x 13,700,920 ops/sec Β±0.38% (96 runs sampled) mean: 0.00007ms +malloc_f32x1024_vanilla x 694,747 ops/sec Β±2.11% (78 runs sampled) mean: 0.00144ms ``` +In this micro benchmark, allocating 4KB blocks (1024 floats) is ~20x +faster than calling `new Float32Array(1024)`. On the other hand, +allocating tiny arrays is slightly slower than the vanilla version... YMMV! + ## Authors - Karsten Schmidt diff --git a/packages/malloc/bench/index.js b/packages/malloc/bench/index.js index 407af966f4..e3e02a6ae3 100644 --- a/packages/malloc/bench/index.js +++ b/packages/malloc/bench/index.js @@ -1,38 +1,45 @@ "use strict"; const Benchmark = require("benchmark"); +const Type = require("@thi.ng/api").Type; +const bench = require("@thi.ng/bench").bench; const tx = require("@thi.ng/transducers"); -const m = require("../index"); +const m = require("../lib/index"); -const pool = new m.MemPool(new ArrayBuffer(1024)); -const Type = m.Type; +const pool = new m.MemPool({ buf: new ArrayBuffer(0x2000) }); const vals = [...tx.map((i) => [...tx.range(1 << i)], tx.range(6))]; -const malloc4 = () => { +const malloc_f64x4 = () => { let a = pool.mallocAs(Type.F64, 4); a.set(vals[2]); - pool.free(a); + pool.freeAll(); }; -const malloc6 = () => { - let a = pool.mallocAs(Type.F64, 8); a.set(vals[3]); - let b = pool.mallocAs(Type.F64, 16); b.set(vals[4]); - let c = pool.mallocAs(Type.F64, 1); c.set(vals[0]); - let d = pool.mallocAs(Type.F64, 32); d.set(vals[5]); - let e = pool.mallocAs(Type.F64, 2); e.set(vals[1]); - let f = pool.mallocAs(Type.F64, 4); f.set(vals[2]); - pool.free(a); - pool.free(b); - pool.free(c); - pool.free(d); - pool.free(e); - pool.free(f); +const malloc6_f64 = () => { + const a = pool.mallocAs(Type.F64, 8); + a.set(vals[3]); + const b = pool.mallocAs(Type.F64, 16); + b.set(vals[4]); + const c = pool.mallocAs(Type.F64, 1); + c.set(vals[0]); + const d = pool.mallocAs(Type.F64, 32); + d.set(vals[5]); + const e = pool.mallocAs(Type.F64, 2); + e.set(vals[1]); + const f = pool.mallocAs(Type.F64, 4); + f.set(vals[2]); + pool.freeAll(); }; -const malloc4vanilla = () => new Float64Array(vals[2]); +const malloc_f32x1024 = () => { + const a = pool.mallocAs(Type.F32, 1024); + pool.freeAll(); +}; + +const malloc_f64x4_vanilla = () => new Float64Array(vals[2]); -const malloc6vanilla = () => { +const malloc6_f64_vanilla = () => { let a = new Float64Array(vals[3]); let b = new Float64Array(vals[4]); let c = new Float64Array(vals[0]); @@ -41,18 +48,29 @@ const malloc6vanilla = () => { let f = new Float64Array(vals[2]); }; +const malloc_f32x1024_vanilla = () => new Float32Array(1024); + new Benchmark.Suite() - .add({ name: "1x f64x4 malloc", fn: malloc4 }) - .add({ name: "1x f64x4 vanilla", fn: malloc4vanilla }) - .add({ name: "6x f64 malloc", fn: malloc6 }) - .add({ name: "6x f64 vanilla", fn: malloc6vanilla }) + .add({ name: "malloc_f64x4", fn: malloc_f64x4 }) + .add({ name: "malloc_f64x4_vanilla", fn: malloc_f64x4_vanilla }) + .add({ name: "malloc6_f64", fn: malloc6_f64 }) + .add({ name: "malloc6_f64_vanilla", fn: malloc6_f64_vanilla }) + .add({ name: "malloc_f32x1024", fn: malloc_f32x1024 }) + .add({ name: "malloc_f32x1024_vanilla", fn: malloc_f32x1024_vanilla }) .on("cycle", (event) => console.log( event.target.toString(), `mean: ${(event.target.stats.mean * 1e3).toFixed(5)}ms` ) ) - .on("complete", function () { + .on("complete", function() { console.log("Fastest is " + this.filter("fastest").map("name")); }) - .run({ "async": false }); + .run({ async: false }); + +bench(malloc_f64x4); +bench(malloc_f64x4_vanilla); +bench(malloc6_f64); +bench(malloc6_f64_vanilla); +bench(malloc_f32x1024); +bench(malloc_f32x1024_vanilla); diff --git a/packages/malloc/package.json b/packages/malloc/package.json index a8f754dc9e..46ab0cf1c1 100644 --- a/packages/malloc/package.json +++ b/packages/malloc/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/malloc", - "version": "4.0.2", + "version": "4.1.0", "description": "ArrayBuffer based malloc() impl for hybrid JS/WASM use cases, based on thi.ng/tinyalloc", "module": "./index.js", "main": "./lib/index.js", @@ -29,14 +29,14 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/binary": "^1.1.0", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/binary": "^1.1.1", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/errors": "^1.2.1" }, "keywords": [ "ES6", diff --git a/packages/malloc/src/api.ts b/packages/malloc/src/api.ts index 5ffd818038..68a650fcf4 100644 --- a/packages/malloc/src/api.ts +++ b/packages/malloc/src/api.ts @@ -1,19 +1,72 @@ -import { IRelease, Type, TypedArray } from "@thi.ng/api"; - -export interface MemBlock { - addr: number; - size: number; - next: MemBlock | null; -} +import { + IRelease, + Type, + TypedArray, + TypedArrayTypeMap +} from "@thi.ng/api"; +import { Pow2 } from "@thi.ng/binary"; export interface MemPoolOpts { - buf: ArrayBuffer; + /** + * Backing ArrayBuffer (or SharedArrayBuffer). If not given, a new + * one will be created with given `size`. + */ + buf: ArrayBufferLike; + /** + * Byte size for newly created ArrayBuffers (if `buf` is not given). + * Default: 0x1000 (4KB) + */ size: number; + /** + * Anchor index (byte address) inside the array buffer. The MemPool + * stores its internal state from the given address and heap space + * starts at least 32 bytes later (depending on chosen `align` + * value). Unlike allocator state variables, `start`` cannot be + * saved inside the array buffer itself. If the ArrayBuffer is + * passed to other consumers they must use the same start value. + * MUST be multiple of 4. Default: 0 + */ start: number; + /** + * Byte address (+1) of the end of the memory region managed by the + * `MemPool`. If not given, defaults to the end of the backing + * ArrayBuffer. + */ end: number; + /** + * Number of bytes to align memory blocks to. MUST be a power of 2 + * and >= 8. Default: 8. Use 16 if the pool is being used for + * allocating memory used in SIMD operations. + */ + align: Pow2; + /** + * Flag to configure memory block compaction. If true (default), + * adjoining free blocks (in terms of address space) will be merged + * to minimize fragementation. + */ compact: boolean; + /** + * Flag to configure memory block splitting. If true (default) and + * when the allocator is re-using a previously freed block larger + * than the requested size, the block will be split to minimize + * wasted/unused memory. The splitting behavior can further + * customized via the `minSplit` option. + */ split: boolean; + /** + * Only used if `split` behavior is enabled. Defines min number of + * excess bytes available in a block for memory block splitting to + * occur. Default: 16. MUST be > 8. + */ minSplit: number; + /** + * Only needed when sharing the underlying ArrayBuffer. If true + * (default: false), the `MemPool` constructor will NOT initialize + * its internal state and assume the underlying ArrayBuffer has + * already been initialized by another `MemPool` instance. If this + * option is used, `buf` MUST be given. + */ + skipInitialization: boolean; } export interface MemPoolStats { @@ -40,23 +93,100 @@ export interface MemPoolStats { } export interface IMemPool extends IRelease { + /** + * Attempts to allocate a new memory block of given `size` (in + * bytes). Returns block address or zero if unsuccessful + * (insufficient memory). + * + * @param size + */ malloc(size: number): number; - calloc(size: number): number; + /** + * Similar to `malloc()`, but if allocation was successful also + * clears the allocated block w/ `fill` value (default: 0). + * + * @param size + * @param fill + */ + calloc(size: number, fill?: number): number; - mallocAs(type: Type, num: number): TypedArray | undefined; + /** + * Takes a `Type` enum and element count `num` (in units of given + * type), calls `malloc()` and if successful wraps allocated block + * as typed array of given `type`. Returns undefined if allocation + * failed. + * + * @param type + * @param num + */ + mallocAs( + type: T, + num: number + ): TypedArrayTypeMap[T] | undefined; - callocAs(type: Type, num: number): TypedArray | undefined; + /** + * Similar to `mallocAs()`, but if allocation was successful also + * clears the allocated block w/ `fill` value (default: 0). + * + * @param type + * @param num + * @param fill + */ + callocAs( + type: T, + num: number, + fill?: number + ): TypedArrayTypeMap[T] | undefined; + /** + * Attempts to reallocate given memory block to new `size` (in + * bytes). If new `size` is larger than the original, attempts to + * grow block or else allocates new one and moves contents to new + * address. If new size is smaller than original, the existing block + * might be split (depending on `split` & `minSplit` config options) + * and the unused part freed. Returns new address if successful or + * zero if re-allocation failed (insufficient menory). + * + * @param ptr + * @param size + */ realloc(ptr: number, size: number): number; - reallocArray(arr: TypedArray, num: number): TypedArray | undefined; + /** + * Similar to `realloc()`, but takes a typed array (one previously + * allocated with `mallocAs()` or `callocAs()`) and if successul + * returns new typed array of same type. Returns undefined on + * failure. + * + * @param arr + * @param num + */ + reallocArray(arr: T, num: number): T | undefined; + /** + * Takes a memory block address and attempts to return the block to + * the pool. Depending on `compact` config option, this operation + * might cause compaction of consecutive free memory blocks to help + * counter fragmentation. Returns true if block has been freed. + * + * It's the user's responsibility to ensure that freed blocks are + * not used any further after calling `free()`. Undefined behavior, + * or worse, pool corruption might ensue! + * + * @param ptr + */ free(ptr: number | TypedArray): boolean; + /** + * Frees all previously allocated blocks and resests allocator state. + */ freeAll(): void; - stats(): MemPoolStats; + /** + * Returns an information object of the pool's state. + */ + stats(): Readonly; } export type BlockCtor = ( diff --git a/packages/malloc/src/index.ts b/packages/malloc/src/index.ts index 3d894796d0..73537bab37 100644 --- a/packages/malloc/src/index.ts +++ b/packages/malloc/src/index.ts @@ -1,3 +1,2 @@ export * from "./api"; export * from "./pool"; -export * from "./wrap"; diff --git a/packages/malloc/src/pool.ts b/packages/malloc/src/pool.ts index 8a2a426b21..c931e22761 100644 --- a/packages/malloc/src/pool.ts +++ b/packages/malloc/src/pool.ts @@ -1,59 +1,87 @@ -import { SIZEOF, Type, TypedArray } from "@thi.ng/api"; -import { align } from "@thi.ng/binary"; +import { + assert, + SIZEOF, + Type, + TypedArray, + typedArray +} from "@thi.ng/api"; +import { align, Pow2 } from "@thi.ng/binary"; import { isNumber } from "@thi.ng/checks"; import { illegalArgs } from "@thi.ng/errors"; -import { - IMemPool, - MemBlock, - MemPoolOpts, - MemPoolStats -} from "./api"; -import { wrap } from "./wrap"; +import { IMemPool, MemPoolOpts, MemPoolStats } from "./api"; + +const STATE_FREE = 0; +const STATE_USED = 1; +const STATE_TOP = 2; +const STATE_END = 3; +const STATE_ALIGN = 4; +const STATE_FLAGS = 5; +const STATE_MIN_SPLIT = 6; + +const MASK_COMPACT = 1; +const MASK_SPLIT = 2; + +const SIZEOF_STATE = 7 * 4; + +const MEM_BLOCK_SIZE = 0; +const MEM_BLOCK_NEXT = 1; + +const SIZEOF_MEM_BLOCK = 2 * 4; export class MemPool implements IMemPool { - buf: ArrayBuffer; - protected top: number; - protected start: number; - protected end: number; - protected doCompact: boolean; - protected doSplit: boolean; - protected minSplit: number; - protected _free: MemBlock | null; - protected _used: MemBlock | null; + buf: ArrayBufferLike; + protected readonly start: number; protected u8: Uint8Array; + protected u32: Uint32Array; + protected state: Uint32Array; constructor(opts: Partial = {}) { this.buf = opts.buf ? opts.buf : new ArrayBuffer(opts.size || 0x1000); + this.start = opts.start != null ? align(Math.max(opts.start, 0), 4) : 0; this.u8 = new Uint8Array(this.buf); - this.start = opts.start != null ? align(Math.max(opts.start, 8), 8) : 8; - this.end = - opts.end != null - ? Math.min(opts.end, this.buf.byteLength) - : this.buf.byteLength; - if (this.start >= this.end) { - illegalArgs( - `invalid address range (0x${this.start.toString( - 16 - )} - 0x${this.end.toString(16)})` + this.u32 = new Uint32Array(this.buf); + this.state = new Uint32Array(this.buf, this.start, SIZEOF_STATE / 4); + + if (!opts.skipInitialization) { + const _align = opts.align || 8; + assert( + _align >= 8, + `invalid alignment: ${_align}, must be a pow2 and >= 8` ); + const top = this.initialTop(_align); + const resolvedEnd = + opts.end != null + ? Math.min(opts.end, this.buf.byteLength) + : this.buf.byteLength; + + if (top >= resolvedEnd) { + illegalArgs( + `insufficient address range (0x${this.start.toString( + 16 + )} - 0x${resolvedEnd.toString(16)})` + ); + } + + this.align = _align; + this.doCompact = opts.compact !== false; + this.doSplit = opts.split !== false; + this.minSplit = opts.minSplit || 16; + this.end = resolvedEnd; + this.top = top; + this._free = 0; + this._used = 0; } - this.top = this.start; - this.doCompact = opts.compact !== false; - this.doSplit = opts.split !== false; - this.minSplit = opts.minSplit || 16; - this._free = null; - this._used = null; } - stats(): MemPoolStats { - const listStats = (block: MemBlock | null) => { + stats(): Readonly { + const listStats = (block: number) => { let count = 0; let size = 0; while (block) { count++; - size += block.size; - block = block.next; + size += this.blockSize(block); + block = this.blockNext(block); } return { count, size }; }; @@ -67,243 +95,397 @@ export class MemPool implements IMemPool { }; } - callocAs(type: Type, num: number): TypedArray | undefined { + callocAs(type: T, num: number, fill = 0) { const block = this.mallocAs(type, num); - block && block.fill(0); + block && block.fill(fill); return block; } - mallocAs(type: Type, num: number): TypedArray | undefined { + mallocAs(type: T, num: number) { const addr = this.malloc(num * SIZEOF[type]); - return addr ? wrap(type, this.buf, addr, num) : undefined; + return addr ? typedArray(type, this.buf, addr, num) : undefined; } - calloc(size: number): number { - const addr = this.malloc(size); - addr && this.u8.fill(0, addr, align(addr + size, 8)); + calloc(bytes: number, fill = 0) { + const addr = this.malloc(bytes); + addr && this.u8.fill(fill, addr, addr + bytes); return addr; } - malloc(size: number): number { - if (size <= 0) { + malloc(bytes: number) { + if (bytes <= 0) { return 0; } - size = align(size, 8); - let top = this.top; + const paddedSize = align(bytes + SIZEOF_MEM_BLOCK, this.align); const end = this.end; + let top = this.top; let block = this._free; - let prev = null; + let prev = 0; while (block) { - const isTop = block.addr + block.size >= top; - if (isTop || block.size >= size) { - if (isTop && block.addr + size > end) { + const blockSize = this.blockSize(block); + const isTop = block + blockSize >= top; + if (isTop || blockSize >= paddedSize) { + if (isTop && block + paddedSize > end) { return 0; } if (prev) { - prev.next = block.next; + this.unlinkBlock(prev, block); } else { - this._free = block.next; + this._free = this.blockNext(block); } - block.next = this._used; + this.setBlockNext(block, this._used); this._used = block; if (isTop) { - block.size = size; - this.top = block.addr + size; + this.top = block + this.setBlockSize(block, paddedSize); } else if (this.doSplit) { - const excess = block.size - size; - if (excess >= this.minSplit) { - block.size = size; - this.insert({ - addr: block.addr + size, - size: excess, - next: null - }); - this.doCompact && this.compact(); - } + const excess = blockSize - paddedSize; + excess >= this.minSplit && + this.splitBlock(block, paddedSize, excess); } - return block.addr; + return blockDataAddress(block); } prev = block; - block = block.next; + block = this.blockNext(block); } - const addr = align(top, 8); - top = addr + size; + block = top; + top = block + paddedSize; if (top <= end) { - block = { - addr, - size, - next: this._used - }; + this.initBlock(block, paddedSize, this._used); this._used = block; this.top = top; - return addr; + return blockDataAddress(block); } return 0; } - realloc(addr: number, size: number) { - if (size <= 0) { + realloc(ptr: number, bytes: number) { + if (bytes <= 0) { return 0; } - size = align(size, 8); + const oldAddr = blockSelfAddress(ptr); + let newAddr = 0; let block = this._used; let blockEnd = 0; - let newAddr = 0; while (block) { - if (block.addr === addr) { - blockEnd = addr + block.size; + if (block === oldAddr) { + const blockSize = this.blockSize(block); + blockEnd = oldAddr + blockSize; const isTop = blockEnd >= this.top; + const paddedSize = align(bytes + SIZEOF_MEM_BLOCK, this.align); // shrink & possibly split existing block - if (size <= block.size) { + if (paddedSize <= blockSize) { if (this.doSplit) { - const excess = block.size - size; + const excess = blockSize - paddedSize; if (excess >= this.minSplit) { - block.size = size; - this.insert({ - addr: block.addr + size, - size: excess, - next: null - }); - this.doCompact && this.compact(); + this.splitBlock(block, paddedSize, excess); } else if (isTop) { - this.top = addr + size; + this.top = oldAddr + paddedSize; } } else if (isTop) { - this.top = addr + size; + this.top = oldAddr + paddedSize; } - newAddr = addr; + newAddr = oldAddr; break; } // try to enlarge block if current top - if (isTop && addr + size < this.end) { - block.size = size; - this.top = addr + size; - newAddr = addr; + if (isTop && oldAddr + paddedSize < this.end) { + this.top = oldAddr + this.setBlockSize(block, paddedSize); + newAddr = oldAddr; break; } // fallback to free & malloc - this.free(addr); - newAddr = this.malloc(size); + this.free(oldAddr); + newAddr = blockSelfAddress(this.malloc(bytes)); break; } - block = block.next; + block = this.blockNext(block); } // copy old block contents to new addr - if (newAddr && newAddr !== addr) { - this.u8.copyWithin(newAddr, addr, blockEnd); + if (newAddr && newAddr !== oldAddr) { + this.u8.copyWithin( + blockDataAddress(newAddr), + blockDataAddress(oldAddr), + blockEnd + ); } - return newAddr; + return blockDataAddress(newAddr); } - reallocArray(ptr: TypedArray, num: number): TypedArray | undefined { - if (ptr.buffer !== this.buf) { + reallocArray(array: T, num: number): T | undefined { + if (array.buffer !== this.buf) { return; } - const addr = this.realloc(ptr.byteOffset, num * ptr.BYTES_PER_ELEMENT); + const addr = this.realloc( + array.byteOffset, + num * array.BYTES_PER_ELEMENT + ); return addr - ? new (ptr.constructor)(this.buf, addr, num) + ? new (array.constructor)(this.buf, addr, num) : undefined; } - free(ptr: number | TypedArray) { + free(ptrOrArray: number | TypedArray) { let addr: number; - if (!isNumber(ptr)) { - if (ptr.buffer !== this.buf) { + if (!isNumber(ptrOrArray)) { + if (ptrOrArray.buffer !== this.buf) { return false; } - addr = ptr.byteOffset; + addr = ptrOrArray.byteOffset; } else { - addr = ptr; + addr = ptrOrArray; } + addr = blockSelfAddress(addr); let block = this._used; - let prev: MemBlock | null = null; + let prev = 0; while (block) { - if (block.addr === addr) { + if (block === addr) { if (prev) { - prev.next = block.next; + this.unlinkBlock(prev, block); } else { - this._used = block.next; + this._used = this.blockNext(block); } this.insert(block); this.doCompact && this.compact(); return true; } prev = block; - block = block.next; + block = this.blockNext(block); } return false; } freeAll() { - this._free = null; - this._used = null; - this.top = this.start; + this._free = 0; + this._used = 0; + this.top = this.initialTop(); } release() { - delete this._free; - delete this._used; delete this.u8; + delete this.u32; + delete this.state; delete this.buf; - delete this.top; - delete this.start; - delete this.end; return true; } + protected get align() { + return this.state[STATE_ALIGN]; + } + + protected set align(x: Pow2) { + this.state[STATE_ALIGN] = x; + } + + protected get end() { + return this.state[STATE_END]; + } + + protected set end(x: number) { + this.state[STATE_END] = x; + } + + protected get top() { + return this.state[STATE_TOP]; + } + + protected set top(x: number) { + this.state[STATE_TOP] = x; + } + + protected get _free() { + return this.state[STATE_FREE]; + } + + protected set _free(block: number) { + this.state[STATE_FREE] = block; + } + + protected get _used() { + return this.state[STATE_USED]; + } + + protected set _used(block: number) { + this.state[STATE_USED] = block; + } + + protected get doCompact() { + return !!(this.state[STATE_FLAGS] & MASK_COMPACT); + } + + protected set doCompact(flag: boolean) { + flag + ? (this.state[STATE_FLAGS] |= 1 << (MASK_COMPACT - 1)) + : (this.state[STATE_FLAGS] &= ~MASK_COMPACT); + } + + protected get doSplit() { + return !!(this.state[STATE_FLAGS] & MASK_SPLIT); + } + + protected set doSplit(flag: boolean) { + flag + ? (this.state[STATE_FLAGS] |= 1 << (MASK_SPLIT - 1)) + : (this.state[STATE_FLAGS] &= ~MASK_SPLIT); + } + + protected get minSplit() { + return this.state[STATE_MIN_SPLIT]; + } + + protected set minSplit(x: number) { + assert( + x > SIZEOF_MEM_BLOCK, + `illegal min split threshold: ${x}, require at least ${SIZEOF_MEM_BLOCK + + 1}` + ); + this.state[STATE_MIN_SPLIT] = x; + } + + protected blockSize(block: number) { + return this.u32[(block >> 2) + MEM_BLOCK_SIZE]; + } + + /** + * Sets & returns given block size. + * + * @param block + * @param size + */ + protected setBlockSize(block: number, size: number) { + this.u32[(block >> 2) + MEM_BLOCK_SIZE] = size; + return size; + } + + protected blockNext(block: number) { + return this.u32[(block >> 2) + MEM_BLOCK_NEXT]; + } + + /** + * Sets block next pointer to `next`. Use zero to indicate list end. + * + * @param block + */ + protected setBlockNext(block: number, next: number) { + this.u32[(block >> 2) + MEM_BLOCK_NEXT] = next; + } + + /** + * Initializes block header with given `size` and `next` pointer. Returns `block`. + * + * @param block + * @param size + * @param next + */ + protected initBlock(block: number, size: number, next: number) { + const idx = block >>> 2; + this.u32[idx + MEM_BLOCK_SIZE] = size; + this.u32[idx + MEM_BLOCK_NEXT] = next; + return block; + } + + protected unlinkBlock(prev: number, block: number) { + this.setBlockNext(prev, this.blockNext(block)); + } + + protected splitBlock(block: number, blockSize: number, excess: number) { + this.insert( + this.initBlock( + block + this.setBlockSize(block, blockSize), + excess, + 0 + ) + ); + this.doCompact && this.compact(); + } + + protected initialTop(_align = this.align) { + return ( + align(this.start + SIZEOF_STATE + SIZEOF_MEM_BLOCK, _align) - + SIZEOF_MEM_BLOCK + ); + } + + /** + * Traverses free list and attempts to recursively merge blocks + * occupying consecutive memory regions. Returns true if any blocks + * have been merged. Only called if `compact` option is enabled. + */ protected compact() { let block = this._free; - let prev: MemBlock | null = null; - let scan: MemBlock | null = null; - let scanPrev: MemBlock; + let prev = 0; + let scan = 0; + let scanPrev: number; let res = false; while (block) { scanPrev = block; - scan = block.next; - while (scan && scanPrev.addr + scanPrev.size === scan.addr) { + scan = this.blockNext(block); + while (scan && scanPrev + this.blockSize(scanPrev) === scan) { // console.log("merge:", scan.addr, scan.size); scanPrev = scan; - scan = scan.next; + scan = this.blockNext(scan); } if (scanPrev !== block) { - const newSize = scanPrev.addr - block.addr + scanPrev.size; + const newSize = scanPrev - block + this.blockSize(scanPrev); // console.log("merged size:", newSize); - block.size = newSize; - const next = scanPrev.next; - let tmp = block.next; - while (tmp && tmp !== scanPrev.next) { + this.setBlockSize(block, newSize); + const next = this.blockNext(scanPrev); + let tmp = this.blockNext(block); + while (tmp && tmp !== next) { // console.log("release:", tmp.addr); - const tn = tmp.next; - tmp.next = null; + const tn = this.blockNext(tmp); + this.setBlockNext(tmp, 0); tmp = tn; } - block.next = next; + this.setBlockNext(block, next); res = true; } // re-adjust top if poss - if (block.addr + block.size >= this.top) { - this.top = block.addr; - prev ? (prev.next = block.next) : (this._free = block.next); + if (block + this.blockSize(block) >= this.top) { + this.top = block; + prev + ? this.unlinkBlock(prev, block) + : (this._free = this.blockNext(block)); } prev = block; - block = block.next; + block = this.blockNext(block); } return res; } - protected insert(block: MemBlock) { + /** + * Inserts given block into list of free blocks, sorted by address. + * + * @param block + */ + protected insert(block: number) { let ptr = this._free; - let prev: MemBlock | null = null; + let prev = 0; while (ptr) { - if (block.addr <= ptr.addr) break; + if (block <= ptr) break; prev = ptr; - ptr = ptr.next; + ptr = this.blockNext(ptr); } if (prev) { - prev.next = block; + this.setBlockNext(prev, block); } else { this._free = block; } - block.next = ptr; + this.setBlockNext(block, ptr); } } + +/** + * Returns a block's data address, based on given alignment. + * + * @param blockAddress + */ +const blockDataAddress = (blockAddress: number) => + blockAddress + SIZEOF_MEM_BLOCK; + +/** + * Returns block start address for given data address and alignment. + * + * @param dataAddress + */ +const blockSelfAddress = (dataAddress: number) => + dataAddress - SIZEOF_MEM_BLOCK; diff --git a/packages/malloc/src/wrap.ts b/packages/malloc/src/wrap.ts deleted file mode 100644 index cdfe4aa251..0000000000 --- a/packages/malloc/src/wrap.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { IObjectOf, Type } from "@thi.ng/api"; - -export type TypedArrayConstructor = - | Uint8ArrayConstructor - | Uint8ClampedArrayConstructor - | Int8ArrayConstructor - | Uint16ArrayConstructor - | Int16ArrayConstructor - | Uint32ArrayConstructor - | Int32ArrayConstructor - | Float32ArrayConstructor - | Float64ArrayConstructor; - -export const TYPEDARRAY_CTORS: IObjectOf = { - [Type.U8]: Uint8Array, - [Type.U8C]: Uint8ClampedArray, - [Type.I8]: Int8Array, - [Type.U16]: Uint16Array, - [Type.I16]: Int16Array, - [Type.U32]: Uint32Array, - [Type.I32]: Int32Array, - [Type.F32]: Float32Array, - [Type.F64]: Float64Array -}; - -export const wrap = (type: Type, buf: ArrayBuffer, addr: number, num: number) => - new TYPEDARRAY_CTORS[type](buf, addr, num); diff --git a/packages/malloc/test/index.ts b/packages/malloc/test/index.ts index 89e3255cd4..c395fad918 100644 --- a/packages/malloc/test/index.ts +++ b/packages/malloc/test/index.ts @@ -1,7 +1,11 @@ -import { Type } from "@thi.ng/api"; +import { Type, TypedArray } from "@thi.ng/api"; +import { align } from "@thi.ng/binary"; import * as assert from "assert"; import { MemPool } from "../src/index"; +const POOL_OVERHEAD = 7 * 4; +const BLOCK_OVERHEAD = 2 * 4; + describe("malloc", () => { let pool: MemPool; @@ -12,14 +16,18 @@ describe("malloc", () => { it("ctor", () => { assert(pool instanceof MemPool); let p: any = pool; - assert.equal(p.start, 0x08); - assert.equal(p.top, 0x08); + assert.equal(p.start, 0); + assert.equal(p.top, align(POOL_OVERHEAD, 8)); assert(p.doCompact); assert(p.doSplit); - assert.equal(p.end, p.buf.byteLength); + assert.equal( + p.end, + p.buf.byteLength, + "When end option not specified, end should be byteLength" + ); p = new MemPool({ size: 0x100, start: 0x0c, end: 0x80 }); - assert.equal(p.start, 0x10); - assert.equal(p.top, 0x10); + assert.equal(p.start, 0x0c); + assert.equal(p.top, align(0x0c + POOL_OVERHEAD, 8)); assert.equal(p.end, 0x80); assert.throws(() => new MemPool({ size: 0x100, start: 0x0, end: 0x0 })); assert.throws( @@ -35,18 +43,22 @@ describe("malloc", () => { assert(!pool.malloc(-1), "neg size"); assert(!pool.malloc(0), "zero size"); + const base = pool.stats().top; let a = pool.malloc(12); let b = pool.malloc(31); let c = pool.malloc(24); - assert.equal(a, 8, "a"); - assert.equal(b, a + 16, "b"); - assert.equal(c, b + 32, "c"); + assert.equal(a, base + BLOCK_OVERHEAD, "a"); + assert.equal(b, a + 16 + BLOCK_OVERHEAD, "b"); + assert.equal(c, b + 32 + BLOCK_OVERHEAD, "c"); // state check let stats = pool.stats(); assert.equal(stats.top, c + 24, "top"); assert.deepEqual(stats.free, { count: 0, size: 0 }); - assert.deepEqual(stats.used, { count: 3, size: 16 + 32 + 24 }); + assert.deepEqual(stats.used, { + count: 3, + size: 16 + 32 + 24 + 3 * BLOCK_OVERHEAD + }); // free all assert(pool.free(a), "free a"); @@ -54,77 +66,91 @@ describe("malloc", () => { assert(pool.free(b), "free c"); assert(!pool.free(b), "free b (repeat)"); stats = pool.stats(); - assert.equal(stats.top, 8, "top2"); + assert.equal(stats.top, base, "top2"); assert.deepEqual(stats.free, { count: 0, size: 0 }); assert.deepEqual(stats.used, { count: 0, size: 0 }); // alloc & split free block a = pool.malloc(32); - assert.equal(a, 8, "a2"); + assert.equal(a, base + BLOCK_OVERHEAD, "a2"); stats = pool.stats(); assert.deepEqual(stats.free, { count: 0, size: 0 }); - assert.deepEqual(stats.used, { count: 1, size: 32 }); - assert.equal(stats.top, 40, "top3"); + assert.deepEqual(stats.used, { count: 1, size: 32 + BLOCK_OVERHEAD }); + assert.equal(stats.top, base + 32 + BLOCK_OVERHEAD, "top3"); // alloc next block & free prev b = pool.malloc(12); - assert.equal(b, 40, "b2"); + assert.equal(b, base + 32 + BLOCK_OVERHEAD * 2, "b2"); assert(pool.free(a), "free a2"); // re-alloc from free & split a = pool.malloc(8); - assert.equal(a, 8, "a3"); + assert.equal(a, base + BLOCK_OVERHEAD, "a3"); stats = pool.stats(); assert.deepEqual(stats.free, { count: 1, size: 24 }); - assert.deepEqual(stats.used, { count: 2, size: 24 }); - assert.equal(stats.top, 56, "top4"); + assert.deepEqual(stats.used, { + count: 2, + size: 24 + 2 * BLOCK_OVERHEAD + }); + assert.equal(stats.top, base + 32 + 16 + 2 * BLOCK_OVERHEAD, "top4"); // join both free blocks assert(pool.free(b), "free b2"); // extend free block + top b = pool.malloc(64); - assert.equal(b, 16, "b3"); + assert.equal(b, base + 8 + 2 * BLOCK_OVERHEAD, "b3"); stats = pool.stats(); assert.deepEqual(stats.free, { count: 0, size: 0 }); - assert.deepEqual(stats.used, { count: 2, size: 72 }); - assert.equal(stats.top, 80, "top5"); + assert.deepEqual(stats.used, { + count: 2, + size: 8 + 64 + 2 * BLOCK_OVERHEAD + }); + assert.equal(stats.top, base + 8 + 64 + 2 * BLOCK_OVERHEAD, "top5"); - // alloc below min size + // alloc at top, below min size c = pool.malloc(1); - // non-continous free chain assert(pool.free(c), "free c2"); - assert.equal(stats.top, 80, "top6"); + // top reset to before + assert.equal(stats.top, base + 8 + 64 + 2 * BLOCK_OVERHEAD, "top6"); assert(pool.free(a), "free a3"); stats = pool.stats(); - assert.deepEqual(stats.free, { count: 1, size: 8 }); - assert.deepEqual(stats.used, { count: 1, size: 64 }); - assert.equal(stats.top, 80, "top7"); + assert.deepEqual(stats.free, { count: 1, size: 8 + BLOCK_OVERHEAD }); + assert.deepEqual(stats.used, { count: 1, size: 64 + BLOCK_OVERHEAD }); + // top remains unchanged + assert.equal(stats.top, base + 8 + 64 + 2 * BLOCK_OVERHEAD, "top7"); // alloc larger size to force walking free chain - // and then alloc @ top (reuse block @ 80) + // and then alloc @ top (reuse earlier block) a = pool.malloc(27); - assert.equal(a, 80, "a4"); + assert.equal(a, base + 8 + 64 + 3 * BLOCK_OVERHEAD, "a4"); stats = pool.stats(); - assert.deepEqual(stats.free, { count: 1, size: 8 }); - assert.deepEqual(stats.used, { count: 2, size: 96 }); - assert.equal(stats.top, 80 + 32, "top8"); + assert.deepEqual(stats.free, { count: 1, size: 8 + BLOCK_OVERHEAD }); + assert.deepEqual(stats.used, { + count: 2, + size: 64 + 32 + 2 * BLOCK_OVERHEAD + }); + assert.equal( + stats.top, + base + 8 + 64 + 32 + 3 * BLOCK_OVERHEAD, + "top8" + ); assert(pool.free(a), "free a4"); assert(pool.free(b), "free b3"); stats = pool.stats(); assert.deepEqual(stats.free, { count: 0, size: 0 }); assert.deepEqual(stats.used, { count: 0, size: 0 }); - assert.equal(stats.available, 256 - 8); - assert.equal(stats.top, 8, "top9"); + assert.equal(stats.available, 256 - base); + assert.equal(stats.top, base, "top9"); pool.freeAll(); assert.deepEqual(pool.stats(), { free: { count: 0, size: 0 }, used: { count: 0, size: 0 }, - available: pool.buf.byteLength - 8, + available: pool.buf.byteLength - base, total: pool.buf.byteLength, - top: 8 + top: base }); pool.release(); }); @@ -136,12 +162,17 @@ describe("malloc", () => { assert.deepEqual(pool.mallocAs(Type.F64, 33), null); assert.deepEqual(pool.mallocAs(Type.U8, -1), null); + const base = pool.stats().top; let a = pool.mallocAs(Type.F32, 3); let b = pool.mallocAs(Type.F64, 3); assert(a instanceof Float32Array, "a type"); assert(b instanceof Float64Array, "b type"); - assert.equal(a!.byteOffset, 8, "a addr"); - assert.equal(b!.byteOffset, 24, "b addr"); + assert.equal(a!.byteOffset, base + BLOCK_OVERHEAD, "a addr"); + assert.equal( + b!.byteOffset, + a!.byteOffset + 16 + BLOCK_OVERHEAD, + "b addr" + ); assert.equal(a!.length, 3, "a.length"); assert.equal(b!.length, 3, "b.length"); assert.equal(a!.byteLength, 12, "a bytes"); @@ -149,20 +180,27 @@ describe("malloc", () => { a!.set([1, 2, 3]); b!.set([10, 20, 30]); assert.deepEqual( - [...new Uint32Array(pool.buf, 8, 10)], + [...new Uint32Array(pool.buf, a!.byteOffset, 4)], [ // a 0x3f800000, 0x40000000, 0x40400000, - 0, + 0 + ] + ); + assert.deepEqual( + [...new Uint32Array(pool.buf, b!.byteOffset, 8)], + [ // b 0, 0x40240000, 0, 0x40340000, 0, - 0x403e0000 + 0x403e0000, + 0, + 0 ] ); assert(pool.free(a!), "free a"); @@ -173,28 +211,33 @@ describe("malloc", () => { }); it("calloc", () => { - const u8 = (pool).u8; - u8.fill(0xff); + const u8: Uint8Array = (pool).u8; + u8.fill(0xff, pool.stats().top); let a = pool.calloc(6); assert.deepEqual( [...u8.subarray(a, a + 9)], - [0, 0, 0, 0, 0, 0, 0, 0, 0xff] + [0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff] ); }); it("callocAs", () => { - let a = pool.callocAs(Type.F32, 3); - let b = pool.callocAs(Type.F64, 3); + const u8: Uint8Array = (pool).u8; + u8.fill(0xff, pool.stats().top); + let a: TypedArray | undefined = pool.callocAs(Type.F32, 3); + let b: TypedArray | undefined = pool.callocAs(Type.F64, 3); + let t = [0, 0, 0]; assert(a instanceof Float32Array, "a type"); assert(b instanceof Float64Array, "b type"); + assert.deepEqual([...a!], t); + assert.deepEqual([...b!], t); a!.set([1, 2, 3]); b!.set([10, 20, 30]); assert(pool.free(a!), "free a"); assert(pool.free(b!), "free b"); - // returned arrays are zeroed - a = pool.callocAs(Type.U32, 3); - b = pool.callocAs(Type.U32, 3); - const t = [0, 0, 0]; + // returned arrays are filled w/ given arg + a = pool.callocAs(Type.U32, 3, 0xaa55aa55); + b = pool.callocAs(Type.U32, 3, 0xaa55aa55); + t = [0xaa55aa55, 0xaa55aa55, 0xaa55aa55]; assert.deepEqual([...a!], t); assert.deepEqual([...b!], t); }); @@ -217,42 +260,153 @@ describe("malloc", () => { pool.free(c); }); - it("realloc"); + it("realloc", () => { + let p: any = pool; + + const a = pool.malloc(8); + p.u8.fill(0xff, a, a + 8); + + const block = p._used; + const bsize = p.blockSize(block); + assert.equal(bsize, align(8 + BLOCK_OVERHEAD, 8), "size a"); + assert.equal(pool.realloc(a, 0), 0, "too small"); + assert.equal(pool.realloc(a, 65), a, "enlarge a"); + + const usedBlockAfterRealloc = p._used; + assert.equal(usedBlockAfterRealloc, block); + assert.equal( + p.blockSize(usedBlockAfterRealloc), + align(65 + BLOCK_OVERHEAD, 8) + ); + + // shrink & update top + assert.equal(pool.realloc(a, 31), a, "shrink a"); + assert.equal( + p.blockSize(usedBlockAfterRealloc), + align(31 + BLOCK_OVERHEAD, 8) + ); + assert.equal(p._free, 0); + assert.equal(p.top, a + 32); + + // add new top block + const b = pool.malloc(8); + assert.equal(b, a + 40, "b"); + + // enlage a again, but need to move after b + const a2 = pool.realloc(a, 65); + assert.equal(a2, b + 16); + assert.deepEqual( + [...p.u8.slice(a2, a2 + 9)], + [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0] + ); + }); + + it("reallocArray", () => { + const a = pool.callocAs(Type.F32, 4, 1); + assert.deepEqual( + [...pool.reallocArray(a!, 8)!], + [1, 1, 1, 1, 0, 0, 0, 0] + ); + assert.equal(pool.reallocArray(a!, 10000), undefined); + assert.equal(pool.reallocArray(new Float32Array(4), 8), undefined); + }); it("no compact", () => { pool = new MemPool({ size: 0x100, compact: false }); - pool.malloc(8); - pool.malloc(8); - pool.malloc(8); - pool.free(8); - pool.free(16); - pool.free(24); - let p: any = pool; - assert.equal(p._free.addr, 8); - assert.equal(p._free.next.addr, 16); - assert.equal(p._free.next.next.addr, 24); - assert.equal(p._free.next.next.next, null); + const p: any = pool; + const a = pool.malloc(8); + const a1 = pool.malloc(8); + const a2 = pool.malloc(8); + pool.free(a); + pool.free(a1); + pool.free(a2); + assert.equal(p._free + BLOCK_OVERHEAD, a); + assert.equal(p.blockNext(p._free) + BLOCK_OVERHEAD, a1); + assert.equal(p.blockNext(p.blockNext(p._free)) + BLOCK_OVERHEAD, a2); + assert.equal(p.blockNext(p.blockNext(p.blockNext(p._free))), 0); }); it("no split", () => { pool = new MemPool({ size: 0x100, split: true }); - pool.malloc(32); + let p: any = pool; + const base = pool.stats().top; + + let a = pool.malloc(32); pool.malloc(8); - pool.free(8); + pool.free(a); pool.malloc(8); - let p: any = pool; - assert.equal(p._used.addr, 8); - assert.equal(p._used.size, 8); - assert.equal(p._free.addr, 16); - assert.equal(p._free.size, 24); + assert.equal(p._used, base); + assert.equal(p.blockSize(p._used), 8 + BLOCK_OVERHEAD); + assert.equal(p._free, base + 8 + BLOCK_OVERHEAD); + assert.equal(p.blockSize(p._free), 24); + pool = new MemPool({ size: 0x100, split: false }); - pool.malloc(32); + p = pool; + a = pool.malloc(32); pool.malloc(8); - pool.free(8); + pool.free(a); pool.malloc(8); - p = pool; - assert.equal(p._used.addr, 8); - assert.equal(p._used.size, 32); - assert.equal(p._free, null); + assert.equal(p._used, base); + assert.equal(p.blockSize(p._used), 32 + BLOCK_OVERHEAD); + assert.equal(p._free, 0); + }); + + it("malloc (align 16)", () => { + pool = new MemPool({ size: 0x100, align: 16 }); + let p: any = pool; + const base = pool.stats().top; + let a = pool.callocAs(Type.U8, 15); + let b = pool.callocAs(Type.U8, 11); + let c = pool.callocAs(Type.U8, 7); + let d = pool.callocAs(Type.U8, 3); + let e = pool.callocAs(Type.U8, 1); + assert.equal(a!.byteOffset, base + BLOCK_OVERHEAD, "a"); + assert.equal( + b!.byteOffset, + align(a!.byteOffset + BLOCK_OVERHEAD + 15, 16), + "b" + ); + assert.equal( + c!.byteOffset, + align(b!.byteOffset + BLOCK_OVERHEAD + 11, 16), + "c" + ); + assert.equal( + d!.byteOffset, + align(c!.byteOffset + BLOCK_OVERHEAD + 7, 16), + "d" + ); + assert.equal( + e!.byteOffset, + align(d!.byteOffset + BLOCK_OVERHEAD + 3, 16), + "e" + ); + let stats = pool.stats(); + assert.equal(stats.top, align(e!.byteOffset + 1, 16) - BLOCK_OVERHEAD); + + pool.free(d!); + assert.equal(p._free, d!.byteOffset - BLOCK_OVERHEAD); + pool.free(b!); + assert.equal(p._free, b!.byteOffset - BLOCK_OVERHEAD); + assert.equal(p.blockNext(p._free), d!.byteOffset - BLOCK_OVERHEAD); + pool.free(c!); + assert.equal(p._free, b!.byteOffset - BLOCK_OVERHEAD); + assert.equal(p.blockSize(p._free), e!.byteOffset - b!.byteOffset); + pool.free(a!); + assert.equal(p._free, a!.byteOffset - BLOCK_OVERHEAD); + assert.equal(p.blockSize(p._free), e!.byteOffset - a!.byteOffset); + pool.free(e!); + assert.equal(p._free, 0); + assert.equal(p._used, 0); + assert.equal(p.top, base); + }); + + it("freeAll (align 16)", () => { + pool = new MemPool({ size: 0x100, align: 16 }); + const base = pool.stats().top; + pool.callocAs(Type.U8, 15); + pool.callocAs(Type.U8, 11); + pool.freeAll(); + assert.equal(pool.stats().top, base); }); }); diff --git a/packages/math/CHANGELOG.md b/packages/math/CHANGELOG.md index 69ba90df77..3ded6b00ca 100644 --- a/packages/math/CHANGELOG.md +++ b/packages/math/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.5.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/math@1.4.2...@thi.ng/math@1.5.0) (2019-11-09) + + +### Features + +* **math:** add mixCubicHermite & tangent fns ([d6b4b37](https://github.com/thi-ng/umbrella/commit/d6b4b3710b80fa1366cb40c193ad745bc63d4253)) + + + + + ## [1.4.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/math@1.4.1...@thi.ng/math@1.4.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/math diff --git a/packages/math/package.json b/packages/math/package.json index bb1c91e95d..95c9f0d205 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/math", - "version": "1.4.2", + "version": "1.5.0", "description": "Assorted common math functions & utilities", "module": "./index.js", "main": "./lib/index.js", @@ -29,8 +29,8 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "keywords": [ "ES6", diff --git a/packages/math/src/mix.ts b/packages/math/src/mix.ts index 14d8d7520f..e26ada3b9d 100644 --- a/packages/math/src/mix.ts +++ b/packages/math/src/mix.ts @@ -45,6 +45,137 @@ export const mixCubic = ( return a * s2 * s + b * 3 * s2 * t + c * 3 * t2 * s + d * t2 * t; }; +/** + * Returns hermite interpolation of `a, b, c, d` at normalized position + * `t`, where `a` and `d` are used as predecessor/successor of `b` / `c` + * and only inform the tangent of the interpolation curve. The + * interpolated result is that of `b` and `c`. + * + * Assumes all inputs are uniformly spaced. If that's not the case, use + * `mixCubicHermite()` with one of the tangent generators supporting + * non-uniform spacing of points. + * + * See: https://www.desmos.com/calculator/j4gf8g9vkr + * + * Source: + * https://www.musicdsp.org/en/latest/Other/93-hermite-interpollation.html + * + * @see mixCubicHermite + * @see tangentCardinal + * @see tangentDiff3 + * + * @param a + * @param b + * @param c + * @param d + * @param t + */ +export const mixHermite = ( + a: number, + b: number, + c: number, + d: number, + t: number +) => { + const y1 = 0.5 * (c - a); + const y2 = 1.5 * (b - c) + 0.5 * (d - a); + return ((y2 * t + a - b + y1 - y2) * t + y1) * t + b; +}; + +/** + * Computes cubic-hermite interpolation between `a` / `b` at normalized + * time `t` and using respective tangents `ta` / `tb`. + * + * https://en.wikipedia.org/wiki/Cubic_Hermite_spline + * + * @see mixHermite + * @see tangentCardinal + * @see tangentDiff3 + * + * @param a + * @param ta + * @param b + * @param tb + * @param t + */ +export const mixCubicHermite = ( + a: number, + ta: number, + b: number, + tb: number, + t: number +) => { + const s = t - 1; + const t2 = t * t; + const s2 = s * s; + const h00 = (1 + 2 * t) * s2; + const h10 = t * s2; + const h01 = t2 * (3 - 2 * t); + const h11 = t2 * s; + return h00 * a + h10 * ta + h01 * b + h11 * tb; +}; + +/** + * Helper function for `mixCubicHermite()`. Computes cardinal tangents + * based on point neighbors of a point B (not given), i.e. `a` + * (predecessor) and `c` (successor) and their times (defaults to + * uniformly spaced). The optional `tension` parameter can be used to + * scale the tangent where 0.0 produces a Cardinal spline tangent and + * 1.0 a Catmull-Rom (opposite to the Wikipedia ref). + * + * https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline + * + * @param prev + * @param next + * @param scale + * @param ta + * @param tc + */ +export const tangentCardinal = ( + prev: number, + next: number, + scale = 0.5, + ta = 0, + tc = 2 +) => scale * ((next - prev) / (tc - ta)); + +/** + * Helper function for `mixCubicHermite()`. Computes tangent for `curr`, + * based on 3-point finite difference, where `prev` & `next` are + * `curr`'s neighbors and the `tX` the three points' respective time + * values. The latter are equally spaced by default (each 1.0 apart). + * + * Using this function with equal spacing of 1.0 and together with + * `mixCubicHermite()` will produce same results as the somewhat + * optimized variant `mixHermite()`. + * + * https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Finite_difference + * + * @param prev + * @param curr + * @param next + * @param ta + * @param tb + * @param tc + */ +export const tangentDiff3 = ( + prev: number, + curr: number, + next: number, + ta = 0, + tb = 1, + tc = 2 +) => 0.5 * ((next - curr) / (tc - tb) + (curr - prev) / (tb - ta)); + +/** + * HOF interpolator. Takes a timing function `f` and interval `[from, + * to]`. Returns function which takes normalized time as single arg and + * returns interpolated value. + * + * @param f + * @param from + * @param to + */ export const tween = (f: (t: number) => number, from: number, to: number) => ( t: number ) => mix(from, to, f(t)); diff --git a/packages/matrices/CHANGELOG.md b/packages/matrices/CHANGELOG.md index cb3f6eba82..3e17caf96f 100644 --- a/packages/matrices/CHANGELOG.md +++ b/packages/matrices/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.5.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/matrices@0.5.8...@thi.ng/matrices@0.5.9) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/matrices + + + + + +## [0.5.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/matrices@0.5.7...@thi.ng/matrices@0.5.8) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/matrices + + + + + +## [0.5.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/matrices@0.5.6...@thi.ng/matrices@0.5.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/matrices + + + + + +## [0.5.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/matrices@0.5.5...@thi.ng/matrices@0.5.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/matrices + + + + + +## [0.5.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/matrices@0.5.4...@thi.ng/matrices@0.5.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/matrices + + + + + +## [0.5.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/matrices@0.5.3...@thi.ng/matrices@0.5.4) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/matrices + + + + + ## [0.5.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/matrices@0.5.2...@thi.ng/matrices@0.5.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/matrices diff --git a/packages/matrices/package.json b/packages/matrices/package.json index a5f8494485..f7d68c375c 100644 --- a/packages/matrices/package.json +++ b/packages/matrices/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/matrices", - "version": "0.5.3", + "version": "0.5.9", "description": "Matrix & quaternion operations for 2D/3D geometry processing", "module": "./index.js", "main": "./lib/index.js", @@ -29,14 +29,14 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/math": "^1.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/math": "^1.5.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2D", diff --git a/packages/matrices/src/frustum.ts b/packages/matrices/src/frustum.ts index 28ceb4e830..9132f667c8 100644 --- a/packages/matrices/src/frustum.ts +++ b/packages/matrices/src/frustum.ts @@ -3,7 +3,8 @@ import { setC } from "@thi.ng/vectors"; import { Mat } from "./api"; /** - * Constructs a M44 representing the given view frustum. + * Constructs a 4x4 matrix representing the given view frustum. Creates + * new matrix if `out` is `null`. * * @param out * @param left diff --git a/packages/matrices/src/invert.ts b/packages/matrices/src/invert.ts index 2112601f24..9080d3da59 100644 --- a/packages/matrices/src/invert.ts +++ b/packages/matrices/src/invert.ts @@ -16,7 +16,11 @@ const dp4 = dotC4; const dp6 = dotC6; /** - * Matrix inversion. + * Matrix inversion. Returns `undefined` if matrix is not invertible. + * Mutates `mat` if `out` is `null`. + * + * @param out + * @param mat */ export const invert: MultiMatOpMU = vop(1); diff --git a/packages/matrices/src/lookat.ts b/packages/matrices/src/lookat.ts index 3c456a9bbd..81ab1759c7 100644 --- a/packages/matrices/src/lookat.ts +++ b/packages/matrices/src/lookat.ts @@ -9,8 +9,9 @@ import { import { Mat } from "./api"; /** - * Constructs a M44 camera matrix for given `eye` position, look-at `target` - * (both in world space) and `up` vector. + * Constructs a 4x4 camera matrix for given `eye` position, look-at + * `target` (both in world space) and normalized `up` vector. Creates + * new matrix if `out` is `null`. * * @param out * @param eye diff --git a/packages/matrices/src/m22-m23.ts b/packages/matrices/src/m22-m23.ts index 6f7648017f..19f162fcb9 100644 --- a/packages/matrices/src/m22-m23.ts +++ b/packages/matrices/src/m22-m23.ts @@ -2,7 +2,8 @@ import { set4 } from "@thi.ng/vectors"; import { MatOpM } from "./api"; /** - * Converts M22 to M23 and writes result to `out`. + * Converts 2x2 to 2x3 matrix and writes result to `out`. Creates new + * matrix if `out` is `null`. * * @param out * @param m22 diff --git a/packages/matrices/src/m23-m22.ts b/packages/matrices/src/m23-m22.ts index 48451632fa..a3d4b625c5 100644 --- a/packages/matrices/src/m23-m22.ts +++ b/packages/matrices/src/m23-m22.ts @@ -2,7 +2,8 @@ import { set4 } from "@thi.ng/vectors"; import { MatOpM } from "./api"; /** - * Converts M23 to M22 and writes result to `out`. + * Converts 2x3 to 2x2 matrix and writes result to `out`. Creates new + * matrix if `out` is `null`. * * @param out * @param m23 diff --git a/packages/matrices/src/m23-m44.ts b/packages/matrices/src/m23-m44.ts index 981e4ed3db..bab36883f1 100644 --- a/packages/matrices/src/m23-m44.ts +++ b/packages/matrices/src/m23-m44.ts @@ -1,20 +1,34 @@ +import { setC } from "@thi.ng/vectors"; import { MatOpM } from "./api"; /** - * Converts M23 to M44 and writes result to `out`. + * Converts 2x3 to 4x4 matrix and writes result to `out`. Creates new + * matrix if `out` is `null`. * * @param out * @param m23 */ -export const mat23to44: MatOpM = (out, m23) => ( - !out && (out = []), - (out[0] = m23[0]), - (out[1] = m23[1]), - (out[4] = m23[2]), - (out[5] = m23[3]), - (out[12] = m23[4]), - (out[13] = m23[5]), - (out[10] = out[15] = 1), - (out[2] = out[3] = out[6] = out[7] = out[8] = out[9] = out[11] = out[14] = 0), - out -); +export const mat23to44: MatOpM = (out, m23) => + setC( + out || [], + // x + m23[0], + m23[1], + 0, + 0, + // y + m23[2], + m23[3], + 0, + 0, + // z + 0, + 0, + 1, + 0, + // w + m23[4], + m23[5], + 0, + 1 + ); diff --git a/packages/matrices/src/m33-m44.ts b/packages/matrices/src/m33-m44.ts index f36fbd4036..20a6a3fca3 100644 --- a/packages/matrices/src/m33-m44.ts +++ b/packages/matrices/src/m33-m44.ts @@ -1,17 +1,34 @@ -import { setS3, setS4, ZERO4 } from "@thi.ng/vectors"; +import { setC } from "@thi.ng/vectors"; import { MatOpM } from "./api"; /** - * Converts M33 to M44 and writes result to `out`. + * Converts 3x3 to 4x4 matrix and writes result to `out`. Creates new + * matrix if `out` is `null`. * * @param out * @param m33 */ -export const mat33to44: MatOpM = (out, m33) => ( - !out && (out = []), - setS3(out, m33, 0, 0), - setS3(out, m33, 4, 3), - setS3(out, m33, 8, 6), - setS3(out, ZERO4, 12), - setS4(out, [0, 0, 0, 1], 3, 0, 4) -); +export const mat33to44: MatOpM = (out, m33) => + setC( + out || [], + // x + m33[0], + m33[1], + m33[2], + 0, + // y + m33[3], + m33[4], + m33[5], + 0, + // z + m33[6], + m33[7], + m33[8], + 0, + // w + 0, + 0, + 0, + 1 + ); diff --git a/packages/matrices/src/m44-m33.ts b/packages/matrices/src/m44-m33.ts index 44331e1065..7cebbfc342 100644 --- a/packages/matrices/src/m44-m33.ts +++ b/packages/matrices/src/m44-m33.ts @@ -2,7 +2,8 @@ import { setS3 } from "@thi.ng/vectors"; import { MatOpM } from "./api"; /** - * Converts M44 to M33 and writes result to `out`. + * Converts 4x4 to 3x3 matrix and writes result to `out`. Creates new + * matrix if `out` is `null`. * * @param out * @param m44 diff --git a/packages/matrices/src/matv.ts b/packages/matrices/src/matv.ts index a4f57d89af..9ecf4ee92e 100644 --- a/packages/matrices/src/matv.ts +++ b/packages/matrices/src/matv.ts @@ -5,10 +5,42 @@ import { setVV9 } from "@thi.ng/vectors"; +/** + * Initializes 2x2 matrix from 2D column vectors. + * + * @param out + * @param x + * @param y + */ export const mat22v = setVV4; +/** + * Initializes 2x3 matrix (affine transform) from 2D column vectors. + * + * @param out + * @param x + * @param y + * @param translate + */ export const mat23v = setVV6; +/** + * Initializes 3x3 matrix from 3D column vectors. + * + * @param out + * @param x + * @param y + * @param z + */ export const mat33v = setVV9; +/** + * Initializes 4x4 matrix from 4D column vectors. + * + * @param out + * @param x + * @param y + * @param z + * @param w + */ export const mat44v = setVV16; diff --git a/packages/matrices/src/mixq.ts b/packages/matrices/src/mixq.ts index c9645127b7..016e300139 100644 --- a/packages/matrices/src/mixq.ts +++ b/packages/matrices/src/mixq.ts @@ -8,10 +8,10 @@ import { } from "@thi.ng/vectors"; /** - * Interpolates quaternion `a` to `b` by given amount `t`, using SLERP. - * Writes result to `out`. The optional `eps` (default 1e-3) is used to - * switch to linear interpolation if the angular difference is very - * small. + * Interpolates quaternion `a` to `b` by given amount `t` [0...1], using + * SLERP. Writes result to `out`. The optional `eps` (default 1e-3) is + * used to switch to linear interpolation if the angular difference is + * very small. * * @param out * @param a diff --git a/packages/matrices/src/mulm.ts b/packages/matrices/src/mulm.ts index 6d0c8e0e28..1c7a0d9ff7 100644 --- a/packages/matrices/src/mulm.ts +++ b/packages/matrices/src/mulm.ts @@ -1,9 +1,17 @@ -import { dotS2, dotS3, dotS4, setC, setC4, setC6, vop } from "@thi.ng/vectors"; +import { + dotS2, + dotS3, + dotS4, + setC, + setC4, + setC6, + vop +} from "@thi.ng/vectors"; import { MultiMatOpMM } from "./api"; /** - * Performs matrix-matrix multiplication. If `out` is not given, writes - * result in `a`. + * Multi-method. Performs matrix-matrix multiplication. If `out` is not + * given, writes result in `a`. * * @param out * @param a @@ -11,6 +19,14 @@ import { MultiMatOpMM } from "./api"; */ export const mulM: MultiMatOpMM = vop(1); +/** + * 2x2 matrix-matrix multiplication. If `out` is not given, writes + * result in `a`. + * + * @param out + * @param a + * @param b + */ export const mulM22 = mulM.add(4, (out, a, b) => setC4( out || a, @@ -21,6 +37,14 @@ export const mulM22 = mulM.add(4, (out, a, b) => ) ); +/** + * 2x3 matrix-matrix multiplication. If `out` is not given, writes + * result in `a`. + * + * @param out + * @param a + * @param b + */ export const mulM23 = mulM.add(6, (out, a, b) => setC6( out || a, @@ -33,6 +57,14 @@ export const mulM23 = mulM.add(6, (out, a, b) => ) ); +/** + * 3x3 matrix-matrix multiplication. If `out` is not given, writes + * result in `a`. + * + * @param out + * @param a + * @param b + */ export const mulM33 = mulM.add(9, (out, a, b) => setC( out || a, @@ -48,6 +80,14 @@ export const mulM33 = mulM.add(9, (out, a, b) => ) ); +/** + * 4x4 matrix-matrix multiplication. If `out` is not given, writes + * result in `a`. + * + * @param out + * @param a + * @param b + */ export const mulM44 = mulM.add(16, (out, a, b) => setC( out || a, diff --git a/packages/matrices/src/mulv.ts b/packages/matrices/src/mulv.ts index 30a0e1667f..9ecfc11bd3 100644 --- a/packages/matrices/src/mulv.ts +++ b/packages/matrices/src/mulv.ts @@ -21,7 +21,7 @@ import { MatOpMV, MultiMatOpMV } from "./api"; export const mulV: MultiMatOpMV = vop(1); /** - * Multiplies M22 `m` with 2D vector `v`. Supports in-place + * Multiplies 2x2 matrix `m` with 2D vector `v`. Supports in-place * modification, i.e. if `out === v`. * * @param out @@ -33,7 +33,7 @@ export const mulV22: MatOpMV = mulV.add(4, (out, m, v) => ); /** - * Multiplies M23 `m` with 2D vector `v`. Supports in-place + * Multiplies 2x3 matrix `m` with 2D vector `v`. Supports in-place * modification, i.e. if `out === v`. * * @param out @@ -45,7 +45,7 @@ export const mulV23: MatOpMV = mulV.add(6, (out, m, v) => ); /** - * Multiplies M33 `m` with 3D vector `v`. Supports in-place + * Multiplies 3x3 matrix `m` with 3D vector `v`. Supports in-place * modification, i.e. if `out === v`. * * @param out @@ -62,7 +62,7 @@ export const mulV33: MatOpMV = mulV.add(9, (out, m, v) => ); /** - * Multiplies M44 `m` with 4D vector `v`. Supports in-place + * Multiplies 4x4 matrix `m` with 4D vector `v`. Supports in-place * modification, i.e. if `out === v`. * * @param out @@ -80,9 +80,9 @@ export const mulV44: MatOpMV = mulV.add(16, (out, m, v) => ); /** - * Multiplies M44 `m` with 3D vector `v` and assumes `w=1`, i.e. the - * vector is interpreted as `[x,y,z,1]`. After transformation applies - * perspective divide of the resulting XYZ components. + * Multiplies 4x4 matrix `m` with 3D vector `v` and assumes `w=1`, i.e. + * the vector is interpreted as `[x,y,z,1]`. After transformation + * applies perspective divide of the resulting XYZ components. * * @param out * @param m diff --git a/packages/matrices/src/normal-mat.ts b/packages/matrices/src/normal-mat.ts index 39b555d925..bdf0a51c75 100644 --- a/packages/matrices/src/normal-mat.ts +++ b/packages/matrices/src/normal-mat.ts @@ -4,10 +4,10 @@ import { mat44to33 } from "./m44-m33"; import { transpose33, transpose44 } from "./transpose"; /** - * Converts given M44 to a M33 normal matrix, i.e. the transposed + * Converts given 4x4 matrix to a 3x3 normal matrix, i.e. the transposed * inverse of its upper-left 3x3 region. If `out` is null a new result * matrix will be created. Returns `undefined` if matrix inversion - * fails. + * failed. * * @param out * @param m @@ -18,9 +18,9 @@ export const normal33: MatOpMU = (out, m) => { }; /** - * Converts given M44 to a M44 normal matrix, i.e. the transposed - * inverse. Writes results to `m` if `out` is null. Returns `undefined` - * if matrix inversion fails. + * Converts given 4x4 matrix to a 4x4 matrix normal matrix, i.e. the + * transposed inverse. Writes results to `m` if `out` is null. Returns + * `undefined` if matrix inversion failed. * * @param out * @param m diff --git a/packages/matrices/src/ortho.ts b/packages/matrices/src/ortho.ts index 71464a4831..144abce0de 100644 --- a/packages/matrices/src/ortho.ts +++ b/packages/matrices/src/ortho.ts @@ -2,8 +2,8 @@ import { setC } from "@thi.ng/vectors"; import { Mat } from "./api"; /** - * Computes a M44 orthographic projection matrix and writes result to - * `out`. + * Creates a 4x4 matrix orthographic projection matrix and writes result + * to `out`. * * @param out * @param left diff --git a/packages/matrices/src/perspective.ts b/packages/matrices/src/perspective.ts index 4c46b80f4e..3a28f5f3bb 100644 --- a/packages/matrices/src/perspective.ts +++ b/packages/matrices/src/perspective.ts @@ -2,8 +2,8 @@ import { Mat } from "./api"; import { frustum, frustumBounds } from "./frustum"; /** - * Computes a M44 perspective projection matrix and writes result to - * `out`. + * Creates a 4x4 matrix perspective projection matrix and writes result + * to `out`. * * @param out * @param fov diff --git a/packages/matrices/src/project.ts b/packages/matrices/src/project.ts index 3c7b78ec35..80bf7450ae 100644 --- a/packages/matrices/src/project.ts +++ b/packages/matrices/src/project.ts @@ -10,14 +10,14 @@ import { invert23, invert44 } from "./invert"; import { mulV23, mulV344, mulV44 } from "./mulv"; /** - * Transforms given point `p` (4D, homogeneous) with M44 `mvp`, applies - * perspective divide and then transforms XY components with M23 `view` - * matrix. Returns 3D vector. The result Z component can be used for - * depth sorting. + * Transforms given point `p` (4D, homogeneous coordinates) with 4x4 + * matrix `mvp`, applies perspective divide and then transforms XY + * components with 2x3 matrix `view` matrix. Returns 3D vector. The + * result Z component can be used for depth sorting. * * @param out - * @param mvp - * @param view + * @param mvp 4x4 matrix + * @param view 2x3 matrix * @param p */ export const project = ( @@ -31,14 +31,14 @@ export const project = ( ); /** - * Reverse operation of project. If `invert` is true (default: false), - * both `mvp` and `view` matrices will be inverted first + * Reverse operation of `project()`. If `invert` is true (default: + * false), both `mvp` and `view` matrices will be inverted first * (non-destructively), else they're both assumed to be inverted * already. * * @param out - * @param mvp - * @param view + * @param mvp 4x4 matrix + * @param view 2x3 matrix * @param p * @param invert */ diff --git a/packages/matrices/src/quat-m33.ts b/packages/matrices/src/quat-m33.ts index f32a704af8..7cbbbc21fe 100644 --- a/packages/matrices/src/quat-m33.ts +++ b/packages/matrices/src/quat-m33.ts @@ -2,7 +2,7 @@ import { ReadonlyVec, setC } from "@thi.ng/vectors"; import { Mat } from "./api"; /** - * Converts quaternion into M33 and writes result to `out`. + * Converts quaternion into 3x3 matrix and writes result to `out`. * * @param out * @param q diff --git a/packages/matrices/src/quat-m44.ts b/packages/matrices/src/quat-m44.ts index 00f3a5464e..c7a84b15a1 100644 --- a/packages/matrices/src/quat-m44.ts +++ b/packages/matrices/src/quat-m44.ts @@ -2,8 +2,8 @@ import { ReadonlyVec, setC, ZERO3 } from "@thi.ng/vectors"; import { Mat } from "./api"; /** - * Converts quaternion into M44 with optional translation offset `t`, - * then writes result to `out`. + * Converts quaternion into 4x4 matrix with optional translation offset + * `t`, then writes result to `out`. * * @param out * @param q diff --git a/packages/matrices/src/rotation-around-axis.ts b/packages/matrices/src/rotation-around-axis.ts index 325ac17915..38e2b41436 100644 --- a/packages/matrices/src/rotation-around-axis.ts +++ b/packages/matrices/src/rotation-around-axis.ts @@ -3,9 +3,9 @@ import { Mat } from "./api"; import { mat33to44 } from "./m33-m44"; /** - * Constructs a M33 representing a rotation of `theta` around `axis` and - * writes result to `out`. If `normalize` is true (default false), - * non-destructively first normalizes axis vector. + * Constructs a 3x3 matrix representing a rotation of `theta` around + * `axis` and writes result to `out`. If `normalize` is true (default + * false), non-destructively first normalizes axis vector. * * @param out * @param axis @@ -37,9 +37,9 @@ export const rotationAroundAxis33 = ( }; /** - * Constructs a M44 representing a rotation of `theta` around `axis` and - * writes result to `out`. If `normalize` is true (default false), - * non-destructively first normalizes axis vector. + * Constructs a 4x4 matrix representing a rotation of `theta` around + * `axis` and writes result to `out`. If `normalize` is true (default + * false), non-destructively first normalizes axis vector. * * @param out * @param axis diff --git a/packages/matrices/src/rotation.ts b/packages/matrices/src/rotation.ts index a2b4b0ea0e..afba5c5b83 100644 --- a/packages/matrices/src/rotation.ts +++ b/packages/matrices/src/rotation.ts @@ -3,7 +3,7 @@ import { setC, setC4, setC6 } from "@thi.ng/vectors"; import { Mat } from "./api"; /** - * Constructs a M22 rotation matrix for given `theta`. + * Constructs a 2x2 matrix rotation matrix for given `theta`. * * @param out * @param theta @@ -14,7 +14,7 @@ export const rotation22 = (out: Mat | null, theta: number) => { }; /** - * Constructs a M23 rotation matrix for given `theta`. + * Constructs a 2x3 matrix rotation matrix for given `theta`. * * @param out * @param theta @@ -25,7 +25,7 @@ export const rotation23 = (out: Mat | null, theta: number) => { }; /** - * Constructs a M33 X rotation matrix for given `theta`. + * Constructs a 3x3 matrix X rotation matrix for given `theta`. * * @param out * @param theta @@ -36,7 +36,7 @@ export const rotationX33 = (out: Mat | null, theta: number) => { }; /** - * Constructs a M33 Y rotation matrix for given `theta`. + * Constructs a 3x3 matrix Y rotation matrix for given `theta`. * * @param out * @param theta @@ -47,7 +47,7 @@ export const rotationY33 = (out: Mat | null, theta: number) => { }; /** - * Constructs a M33 Z rotation matrix for given `theta`. + * Constructs a 3x3 matrix Z rotation matrix for given `theta`. * * @param out * @param theta @@ -58,7 +58,7 @@ export const rotationZ33 = (out: Mat | null, theta: number) => { }; /** - * Constructs a M44 X rotation matrix for given `theta`. + * Constructs a 4x4 matrix X rotation matrix for given `theta`. * * @param out * @param theta @@ -69,7 +69,7 @@ export const rotationX44 = (out: Mat | null, theta: number) => { }; /** - * Constructs a M44 Y rotation matrix for given `theta`. + * Constructs a 4x4 matrix Y rotation matrix for given `theta`. * * @param out * @param theta @@ -80,7 +80,7 @@ export const rotationY44 = (out: Mat | null, theta: number) => { }; /** - * Constructs a M44 Z rotation matrix for given `theta`. + * Constructs a 4x4 matrix Z rotation matrix for given `theta`. * * @param out * @param theta diff --git a/packages/matrices/src/scale-center.ts b/packages/matrices/src/scale-center.ts index 657306d2ac..4eb54e0947 100644 --- a/packages/matrices/src/scale-center.ts +++ b/packages/matrices/src/scale-center.ts @@ -5,8 +5,8 @@ import { scale23, scale44 } from "./scale"; import { translation23, translation44 } from "./translation"; /** - * Computes a M23 representing a scale operation with origin `p` and - * writes result to `out`. + * Computes a 2x3 matrix representing a scale operation with origin `p` + * and writes result to `out`. * * @param out * @param m @@ -24,8 +24,8 @@ export const scaleWithCenter23 = ( ); /** - * Computes a M44 representing a scale operation with origin `p` and - * writes result to `out`. + * Computes a 4x4 matrix representing a scale operation with origin `p` + * and writes result to `out`. * * @param out * @param m diff --git a/packages/matrices/src/scale.ts b/packages/matrices/src/scale.ts index 73c0dbbc45..540a5c2987 100644 --- a/packages/matrices/src/scale.ts +++ b/packages/matrices/src/scale.ts @@ -8,8 +8,8 @@ import { import { Mat } from "./api"; /** - * Computes M22 scale matrix and writes result to `out`. If `s` is a - * number, scaling will be uniform. + * Computes 2x2 matrix scale matrix and writes result to `out`. If `s` + * is a number, scaling will be uniform. * * @param m * @param s @@ -19,8 +19,8 @@ export const scale22 = (m: Mat | null, s: number | ReadonlyVec) => ( ); /** - * Computes M23 scale matrix and writes result to `out`. If `s` is a - * number, scaling will be uniform. + * Computes 2x3 matrix scale matrix and writes result to `out`. If `s` + * is a number, scaling will be uniform. * * @param m * @param s @@ -30,8 +30,8 @@ export const scale23 = (m: Mat | null, s: number | ReadonlyVec) => ( ); /** - * Computes M33 scale matrix and writes result to `out`. If `s` is a - * number, scaling will be uniform. + * Computes 3x3 matrix scale matrix and writes result to `out`. If `s` + * is a number, scaling will be uniform. * * @param m * @param s @@ -42,8 +42,8 @@ export const scale33 = (m: Mat | null, s: number | ReadonlyVec) => ( ); /** - * Computes M44 scale matrix and writes result to `out`. If `s` is a - * number, scaling will be uniform. + * Computes 4x4 matrix scale matrix and writes result to `out`. If `s` + * is a number, scaling will be uniform. * * @param m * @param s @@ -52,18 +52,22 @@ export const scale44 = (m: Mat | null, s: number | ReadonlyVec) => ( (s = isNumber(s) ? [s, s, s] : s), setC( m || [], + // x s[0], 0, 0, 0, + // y 0, s[1], 0, 0, + // z 0, 0, s[2], 0, + // w 0, 0, 0, diff --git a/packages/matrices/src/transform.ts b/packages/matrices/src/transform.ts index e4fe27fdfb..f7ff0474b9 100644 --- a/packages/matrices/src/transform.ts +++ b/packages/matrices/src/transform.ts @@ -9,7 +9,7 @@ import { scale23, scale44 } from "./scale"; import { translation23 } from "./translation"; /** - * Creates M23 TRS transformation matrix from given translation vector, + * Creates 2x3 TRS transformation matrix from given translation vector, * rotation angle and scale factor/vector. * * @param out @@ -31,10 +31,11 @@ export const transform23 = ( ); /** - * Creates M44 TRS transformation matrix from given translation vector, + * Creates 4x4 TRS transformation matrix from given translation vector, * rotation angles (given as 3D vector) and scale factor/vector. * Internally, uses a quaternion for constructing the rotation matrix - * part. + * part. The quaternion is created via `quatFromEuler()` with ZYX + * ordering. * * @param out * @param translate diff --git a/packages/matrices/src/translation.ts b/packages/matrices/src/translation.ts index d5dd010783..cdc6c77bae 100644 --- a/packages/matrices/src/translation.ts +++ b/packages/matrices/src/translation.ts @@ -2,7 +2,7 @@ import { ReadonlyVec, setC, setC6 } from "@thi.ng/vectors"; import { Mat } from "./api"; /** - * Constructs a M23 translation matrix. + * Constructs a 2x3 translation matrix. * * @param out * @param v @@ -11,7 +11,7 @@ export const translation23 = (m: Mat | null, v: ReadonlyVec) => setC6(m || [], 1, 0, 0, 1, v[0], v[1]); /** - * Constructs a M44 translation matrix. + * Constructs a 4x4 translation matrix. * * @param out * @param v diff --git a/packages/matrices/src/transpose.ts b/packages/matrices/src/transpose.ts index 4e841140f8..a7de851cc5 100644 --- a/packages/matrices/src/transpose.ts +++ b/packages/matrices/src/transpose.ts @@ -2,7 +2,8 @@ import { setC, setC4 } from "@thi.ng/vectors"; import { MatOpM } from "./api"; /** - * Writes transposition of M22 `m` to `out`. + * Writes transposition of 2x2 matrix `m` to `out`. Creates new matrix + * if `out` is `null` * * @param out * @param m @@ -11,7 +12,8 @@ export const transpose22: MatOpM = (out, m) => setC4(out || [], m[0], m[2], m[1], m[3]); /** - * Writes transposition of M33 `m` to `out`. + * Writes transposition of 3x3 matrix `m` to `out`. Creates new matrix + * if `out` is `null` * * @param out * @param m @@ -20,7 +22,8 @@ export const transpose33: MatOpM = (out, m) => setC(out || [], m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8]); /** - * Writes transposition of M44 `m` to `out`. + * Writes transposition of 4x4 matrix `m` to `out`. Creates new matrix + * if `out` is `null` * * @param out * @param m diff --git a/packages/matrices/src/viewport.ts b/packages/matrices/src/viewport.ts index 8e09a472a6..2c42b5f27b 100644 --- a/packages/matrices/src/viewport.ts +++ b/packages/matrices/src/viewport.ts @@ -4,7 +4,7 @@ import { scale23 } from "./scale"; import { translation23 } from "./translation"; /** - * Produces a M23 viewport matrix to transform projected coordinates to + * Produces a 2x3 viewport matrix to transform projected coordinates to * screen space. * * @param out diff --git a/packages/memoize/CHANGELOG.md b/packages/memoize/CHANGELOG.md index 1dbf8e92a6..62981804a4 100644 --- a/packages/memoize/CHANGELOG.md +++ b/packages/memoize/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/memoize@1.1.4...@thi.ng/memoize@1.1.5) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/memoize + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/memoize@1.1.3...@thi.ng/memoize@1.1.4) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/memoize + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/memoize@1.1.2...@thi.ng/memoize@1.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/memoize + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/memoize@1.1.1...@thi.ng/memoize@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/memoize diff --git a/packages/memoize/package.json b/packages/memoize/package.json index aaf7f77fe4..43ea68d39c 100644 --- a/packages/memoize/package.json +++ b/packages/memoize/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/memoize", - "version": "1.1.2", + "version": "1.1.5", "description": "Function memoization with configurable caches", "module": "./index.js", "main": "./lib/index.js", @@ -29,11 +29,11 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2" + "@thi.ng/api": "^6.5.0" }, "keywords": [ "cache", diff --git a/packages/morton/CHANGELOG.md b/packages/morton/CHANGELOG.md index c0ccaa0480..41b583aa68 100644 --- a/packages/morton/CHANGELOG.md +++ b/packages/morton/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/morton@1.1.3...@thi.ng/morton@1.1.4) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/morton + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/morton@1.1.2...@thi.ng/morton@1.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/morton + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/morton@1.1.1...@thi.ng/morton@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/morton diff --git a/packages/morton/package.json b/packages/morton/package.json index 660298826f..f7497fa567 100644 --- a/packages/morton/package.json +++ b/packages/morton/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/morton", - "version": "1.1.2", + "version": "1.1.4", "description": "Z-order-curve / Morton encoding & decoding for 1D, 2D, 3D", "module": "./index.js", "main": "./lib/index.js", @@ -29,13 +29,13 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/binary": "^1.1.0", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/math": "^1.4.2" + "@thi.ng/binary": "^1.1.1", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/math": "^1.5.0" }, "keywords": [ "binary", diff --git a/packages/paths/CHANGELOG.md b/packages/paths/CHANGELOG.md index 53251cfc9f..96ef7d22a3 100644 --- a/packages/paths/CHANGELOG.md +++ b/packages/paths/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/paths@2.1.5...@thi.ng/paths@2.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/paths + + + + + +## [2.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/paths@2.1.4...@thi.ng/paths@2.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/paths + + + + + +## [2.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/paths@2.1.3...@thi.ng/paths@2.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/paths + + + + + +## [2.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/paths@2.1.2...@thi.ng/paths@2.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/paths + + + + + ## [2.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/paths@2.1.1...@thi.ng/paths@2.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/paths diff --git a/packages/paths/package.json b/packages/paths/package.json index cf16bcc1e1..8c1922fb13 100644 --- a/packages/paths/package.json +++ b/packages/paths/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/paths", - "version": "2.1.2", + "version": "2.1.6", "description": "immutable, optimized path-based object property / array accessors", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/checks": "^2.2.2", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/checks": "^2.4.1", + "@thi.ng/errors": "^1.2.1" }, "keywords": [ "accessors", diff --git a/packages/pixel/CHANGELOG.md b/packages/pixel/CHANGELOG.md index fa7256acca..a6df3ef1aa 100644 --- a/packages/pixel/CHANGELOG.md +++ b/packages/pixel/CHANGELOG.md @@ -3,6 +3,49 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/pixel@0.1.4...@thi.ng/pixel@0.1.5) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/pixel + + + + + +## [0.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/pixel@0.1.3...@thi.ng/pixel@0.1.4) (2019-09-21) + + +### Bug Fixes + +* **pixel:** clamp values in PackedChannel.setFloat() ([ce78467](https://github.com/thi-ng/umbrella/commit/ce78467)) + + + + + +## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/pixel@0.1.2...@thi.ng/pixel@0.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/pixel + + + + + +## [0.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/pixel@0.1.1...@thi.ng/pixel@0.1.2) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/pixel + + + + + +## [0.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/pixel@0.1.0...@thi.ng/pixel@0.1.1) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/pixel + + + + + # 0.1.0 (2019-07-31) diff --git a/packages/pixel/README.md b/packages/pixel/README.md index 6d75bf422c..aa1b24974a 100644 --- a/packages/pixel/README.md +++ b/packages/pixel/README.md @@ -23,19 +23,19 @@ This project is part of the ## About -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/screenshots/pixel-basics.jpg) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/pixel/pixel-basics.png) -Typed array backed, packed integer and non-packed float pixel buffers -with customizable layout formats and the following operations: +Typed array backed, packed integer pixel buffers with customizable +layout formats and the following operations: - Buffer creation from HTML image elements w/ opt resize & format conversion (browser only) - Buffer-to-buffer blitting w/ automatic format conversion - Buffer-to-canvas blitting - Buffer-to-buffer blending w/ [Porter-Duff - operators](https://github.com/thi-ng/umbrella/tree/master/packages/color#rgba-porter-duff-compositing) + operators](https://github.com/thi-ng/umbrella/tree/master/packages/porter-duff) - Pre/post-multiply alpha -- Region / subimage extraction +- Region / sub-image extraction - Single-channel manipulation / extraction / replacement / conversion - Inversion - XY pixel accessors @@ -54,16 +54,16 @@ with customizable layout formats and the following operations: ### Preset pixel formats -All formats use the canvas native ABGR 32bit format as common -intermediate for conversions. During conversion to ABGR, channels with -sizes smaller than 8 bits will be scaled appropriately to ensure an as -full-range and as linear as possible mapping. E.g. a 4 bit channel will -be scaled by 255 / 15 = 17. +All packed integer formats use the canvas native ABGR 32bit format as +common intermediate for conversions. During conversion to ABGR, channels +with sizes smaller than 8 bits will be scaled appropriately to ensure an +as full-range and as linear as possible mapping. E.g. a 4 bit channel +will be scaled by 255 / 15 = 17. Format specs can freely control channel layout within current limits: -- Channel sizes: 1-8 bits. -- Storage: 8-32 bits per pixel +- Channel sizes: 1 - 32 bits. +- Storage: 8, 16 or 32 bits per pixel | Format ID | Bits per pixel | Description | |----------------|-------------------|------------------------------------------------------| @@ -97,22 +97,23 @@ yarn add @thi.ng/pixel - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/master/packages/checks) - [@thi.ng/math](https://github.com/thi-ng/umbrella/tree/master/packages/math) +- [@thi.ng/porter-duff](https://github.com/thi-ng/umbrella/tree/master/packages/porter-duff) ## Usage examples Porter-Duff operators: [Live demo](http://demo.thi.ng/umbrella/porter-duff/) | -[Source](https://github.com/thi-ng/umbrella/tree/develop/examples/porter-duff) +[Source](https://github.com/thi-ng/umbrella/tree/master/examples/porter-duff) -![porter-duff compositing modes](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/porter-duff2.png) +![porter-duff compositing modes](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/porter-duff/porter-duff2.png) -Code for the above example / screenshot... +Code for the screenshot at the top of this readme... Also see full example here: [Live demo](http://demo.thi.ng/umbrella/pixel-basics/) | -[Source](https://github.com/thi-ng/umbrella/tree/develop/examples/pixel-basics) +[Source](https://github.com/thi-ng/umbrella/tree/master/examples/pixel-basics) ```ts import * as pix from "@thi.ng/pixel"; @@ -178,7 +179,7 @@ Promise ## API -TODO see example & source comments for now +TODO see examples & source comments for now ## Authors diff --git a/packages/pixel/package.json b/packages/pixel/package.json index 5f2488a097..557dd3e9e5 100644 --- a/packages/pixel/package.json +++ b/packages/pixel/package.json @@ -1,7 +1,7 @@ { "name": "@thi.ng/pixel", - "version": "0.1.0", - "description": "TODO", + "version": "0.1.5", + "description": "Packed pixel buffer w/ customizable formats, blitting, conversions", "module": "./index.js", "main": "./lib/index.js", "umd:main": "./lib/index.umd.js", @@ -29,14 +29,14 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/math": "^1.4.2", - "@thi.ng/porter-duff": "^0.1.0" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/math": "^1.5.0", + "@thi.ng/porter-duff": "^0.1.4" }, "keywords": [ "ES6", diff --git a/packages/pixel/src/format.ts b/packages/pixel/src/format.ts index 583b4cf4a9..f79f99ecc4 100644 --- a/packages/pixel/src/format.ts +++ b/packages/pixel/src/format.ts @@ -1,4 +1,5 @@ import { assert, Type } from "@thi.ng/api"; +import { clamp01 } from "@thi.ng/math"; import { Lane, PackedChannel, @@ -31,7 +32,7 @@ const defChannel = ( int, setInt, float: (x) => int(x) / mask0, - setFloat: (src, x) => setInt(src, x * mask0) + setFloat: (src, x) => setInt(src, clamp01(x) * mask0) }; }; diff --git a/packages/pixel/src/packed.ts b/packages/pixel/src/packed.ts index 56172ba0ce..e921312b4a 100644 --- a/packages/pixel/src/packed.ts +++ b/packages/pixel/src/packed.ts @@ -20,7 +20,11 @@ import { clampRegion, ensureChannel, ensureSize, - prepRegions + prepRegions, + setChannelConvert, + setChannelSame, + setChannelUni, + transformABGR } from "./utils"; interface UIntArrayConstructor { @@ -235,26 +239,21 @@ export class PackedBuffer { const dbuf = this.pixels; const set = chan.setInt; if (isNumber(src)) { - for (let i = dbuf.length; --i >= 0; ) { - dbuf[i] = set(dbuf[i], src); - } + setChannelUni(dbuf, src, set); } else { const sbuf = src.pixels; const schan = src.format.channels[0]; ensureSize(sbuf, this.width, this.height); if (chan.size === schan.size) { - const get = schan.int; - for (let i = dbuf.length; --i >= 0; ) { - dbuf[i] = set(dbuf[i], get(sbuf[i])); - } + setChannelSame(dbuf, sbuf, schan.int, set); } else { - const sto = src.format.toABGR; - const from = this.format.fromABGR; - const mask = chan.maskA; - const invMask = ~mask; - for (let i = dbuf.length; --i >= 0; ) { - dbuf[i] = (dbuf[i] & invMask) | (from(sto(sbuf[i])) & mask); - } + setChannelConvert( + dbuf, + sbuf, + this.format.fromABGR, + src.format.toABGR, + chan.maskA + ); } } return this; @@ -271,22 +270,12 @@ export class PackedBuffer { } premultiply() { - const pix = this.pixels; - const from = this.format.fromABGR; - const to = this.format.toABGR; - for (let i = pix.length; --i >= 0; ) { - pix[i] = from(premultiplyInt(to(pix[i]))); - } + transformABGR(this.pixels, this.format, premultiplyInt); return this; } postmultiply() { - const pix = this.pixels; - const from = this.format.fromABGR; - const to = this.format.toABGR; - for (let i = pix.length; --i >= 0; ) { - pix[i] = from(postmultiplyInt(to(pix[i]))); - } + transformABGR(this.pixels, this.format, postmultiplyInt); return this; } diff --git a/packages/pixel/src/utils.ts b/packages/pixel/src/utils.ts index 6807fb801c..c986460e40 100644 --- a/packages/pixel/src/utils.ts +++ b/packages/pixel/src/utils.ts @@ -1,4 +1,10 @@ -import { assert, TypedArray } from "@thi.ng/api"; +import { + assert, + Fn, + Fn2, + TypedArray, + UIntArray +} from "@thi.ng/api"; import { clamp } from "@thi.ng/math"; import { BlitOpts, PackedFormat } from "./api"; @@ -64,3 +70,49 @@ export const prepRegions = ( ); return { sx, sy, dx, dy, rw, rh }; }; + +export const setChannelUni = ( + dbuf: UIntArray, + src: number, + set: Fn2 +) => { + for (let i = dbuf.length; --i >= 0; ) { + dbuf[i] = set(dbuf[i], src); + } +}; + +export const setChannelSame = ( + dbuf: UIntArray, + sbuf: UIntArray, + get: Fn, + set: Fn2 +) => { + for (let i = dbuf.length; --i >= 0; ) { + dbuf[i] = set(dbuf[i], get(sbuf[i])); + } +}; + +export const setChannelConvert = ( + dbuf: UIntArray, + sbuf: UIntArray, + from: Fn, + sto: Fn, + mask: number +) => { + const invMask = ~mask; + for (let i = dbuf.length; --i >= 0; ) { + dbuf[i] = (dbuf[i] & invMask) | (from(sto(sbuf[i])) & mask); + } +}; + +export const transformABGR = ( + pix: UIntArray, + format: PackedFormat, + fn: Fn +) => { + const from = format.fromABGR; + const to = format.toABGR; + for (let i = pix.length; --i >= 0; ) { + pix[i] = from(fn(to(pix[i]))); + } +}; diff --git a/packages/pointfree-lang/CHANGELOG.md b/packages/pointfree-lang/CHANGELOG.md index ad9dba45c7..3440ff6410 100644 --- a/packages/pointfree-lang/CHANGELOG.md +++ b/packages/pointfree-lang/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@1.1.5...@thi.ng/pointfree-lang@1.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/pointfree-lang + + + + + +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@1.1.4...@thi.ng/pointfree-lang@1.1.5) (2019-09-21) + + +### Bug Fixes + +* **pointfree-lang:** update imports ([8de1366](https://github.com/thi-ng/umbrella/commit/8de1366)) + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@1.1.3...@thi.ng/pointfree-lang@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/pointfree-lang + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@1.1.2...@thi.ng/pointfree-lang@1.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/pointfree-lang + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@1.1.1...@thi.ng/pointfree-lang@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/pointfree-lang diff --git a/packages/pointfree-lang/package.json b/packages/pointfree-lang/package.json index b7c8589e3e..478955c7a4 100644 --- a/packages/pointfree-lang/package.json +++ b/packages/pointfree-lang/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/pointfree-lang", - "version": "1.1.2", + "version": "1.1.6", "description": "Forth style syntax layer/compiler for the @thi.ng/pointfree DSL", "module": "./index.js", "main": "./lib/index.js", @@ -31,13 +31,13 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "pegjs": "^0.11.0-dev.325", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/pointfree": "^1.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/pointfree": "^1.2.2" }, "keywords": [ "concatenative", diff --git a/packages/pointfree-lang/test/readme.ts b/packages/pointfree-lang/test/readme.ts index c124a8c57f..5ee44854a7 100644 --- a/packages/pointfree-lang/test/readme.ts +++ b/packages/pointfree-lang/test/readme.ts @@ -1,4 +1,4 @@ -import { StackContext } from "@thi.ng/pointfree/api"; +import { StackContext } from "@thi.ng/pointfree"; import * as pf from "../src"; const src = ` diff --git a/packages/pointfree/CHANGELOG.md b/packages/pointfree/CHANGELOG.md index 9100b5bcfc..3a13809aa9 100644 --- a/packages/pointfree/CHANGELOG.md +++ b/packages/pointfree/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@1.2.1...@thi.ng/pointfree@1.2.2) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/pointfree + + + + + +## [1.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@1.2.0...@thi.ng/pointfree@1.2.1) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/pointfree + + + + + +# [1.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@1.1.3...@thi.ng/pointfree@1.2.0) (2019-08-21) + + +### Features + +* **pointfree:** add new r-stack words, refactor ([dbad162](https://github.com/thi-ng/umbrella/commit/dbad162)) + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@1.1.2...@thi.ng/pointfree@1.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/pointfree + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@1.1.1...@thi.ng/pointfree@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/pointfree diff --git a/packages/pointfree/README.md b/packages/pointfree/README.md index 78fdacc6cd..f39306451b 100644 --- a/packages/pointfree/README.md +++ b/packages/pointfree/README.md @@ -57,7 +57,7 @@ Current features: - words implemented as tiny vanilla JS functions (easily extensible) - optimized pre-composition/compilation of custom user defined words (see - [comp.ts](https://github.com/thi-ng/umbrella/tree/master/packages/pointfree/src/comp.ts)) + [word.ts](https://github.com/thi-ng/umbrella/tree/master/packages/pointfree/src/word.ts)) - dual stack (main & stash/scratch space) - nested execution environments (scopes) - arbitrary stack values @@ -204,7 +204,7 @@ The latter are replaced by calls to `push` which pushes the given value on the stack as is. Therefore, a stack program like: `[1, 2, pf.add]` compiles to: -``` +```ts pf.add(pf.push(2)(pf.push(1)())) ``` @@ -213,7 +213,7 @@ pf.add(pf.push(2)(pf.push(1)())) Most concatenative languages use stack effect comments as the standard approach to document the effect a word has on the stack structure. -``` +```forth ( x y -- x ) ``` @@ -361,7 +361,7 @@ Since quoatations are just arrays, we can treat them as data, i.e. **the functional composition of two quotations is the same as concatenating two arrays**: -``` +```js const add10 = [10, pf.add]; const mul10 = [10, pf.mul]; @@ -786,157 +786,157 @@ at word construction time and return a pre-configured stack function. ### D-Stack modification -| Word | Stack effect | Description | -| --- | --- | --- | -| `drop` | `( x -- )` | remove TOS | -| `drop2` | `( x y -- )` | remove top 2 vals | -| `dropif` | `( x -- ? )` | remove only if TOS truthy | -| `dsp` | `( -- stack.length )` | push d-stack depth | -| `dup` | `( x -- x x )` | duplicate TOS | -| `dup2` | `( x y -- x y x y )` | duplicate top 2 vals | -| `dup3` | `( x y z -- x y z x y z )` | duplicate top 3 vals | -| `dupif` | `( x -- x x? )` | dup only if TOS truthy | -| `maptos(fn)` | `( x -- f(x) )` | transform TOS w/ `f` | -| `map2(fn)` | `( x y -- f(y, x) )` | reduce top 2 vals with `f`, single result | -| `nip` | `( x y -- y )` | remove `x` from stack | -| `over` | `( x y -- x y x )` | push dup of `x` | -| `pick` | `( n -- stack[n] )` | dup deeper stack value | -| `push(...args)` | `( -- ...args )` | push `args` on stack | -| `rot` | `( x y z -- y z x )` | rotate top 3 vals down/left | -| `invrot` | `( x y z -- z x y )` | rotate top 3 vals up/right | -| `swap` | `( x y -- y x )` | swap top 2 vals | -| `swap2` | `( a b c d -- c d a b )` | swap top 2 pairs | -| `tuck` | `( x y -- y x y )` | insert dup of TOS | +| Word | Stack effect | Description | +|-----------------|----------------------------|-------------------------------------------| +| `drop` | `( x -- )` | remove TOS | +| `drop2` | `( x y -- )` | remove top 2 vals | +| `dropif` | `( x -- ? )` | remove only if TOS truthy | +| `dsp` | `( -- stack.length )` | push d-stack depth | +| `dup` | `( x -- x x )` | duplicate TOS | +| `dup2` | `( x y -- x y x y )` | duplicate top 2 vals | +| `dup3` | `( x y z -- x y z x y z )` | duplicate top 3 vals | +| `dupif` | `( x -- x x? )` | dup only if TOS truthy | +| `maptos(fn)` | `( x -- f(x) )` | transform TOS w/ `f` | +| `map2(fn)` | `( x y -- f(y, x) )` | reduce top 2 vals with `f`, single result | +| `nip` | `( x y -- y )` | remove `x` from stack | +| `over` | `( x y -- x y x )` | push dup of `x` | +| `pick` | `( n -- stack[n] )` | dup deeper stack value | +| `push(...args)` | `( -- ...args )` | push `args` on stack | +| `rot` | `( x y z -- y z x )` | rotate top 3 vals down/left | +| `invrot` | `( x y z -- z x y )` | rotate top 3 vals up/right | +| `swap` | `( x y -- y x )` | swap top 2 vals | +| `swap2` | `( a b c d -- c d a b )` | swap top 2 pairs | +| `tuck` | `( x y -- y x y )` | insert dup of TOS | ### R-Stack modification -| Word | Stack effect | Description | -| --- | --- | --- | -| `rdrop` | `( x -- )` | drop TOS from r-stack | -| `rdrop2` | `( x y -- )` | remove top 2 vals from r-stack | -| `rswap` | `( x y -- y x )` | swap top 2 vals on r-stack | -| `rswap2` | `( a b c d -- c d a b )` | swap top 2 pairs on r-stack | -| `rsp` | `( -- stack.length )` | push r-stack depth on d-stack | -| `movdr` | `( x -- )` (d-stack effect) | push d-stack TOS on r-stack | -| `movrd` | `( -- x )` (d-stack effect) | push r-stack TOS on d-stack | -| `cpdr` | `( x -- x )` (d-stack effect) | copy d-stack TOS on r-stack | -| `cprd` | `( -- x )` (d-stack effect) | copy r-stack TOS on d-stack | +| Word | Stack effect | Description | +|----------|-------------------------------|--------------------------------| +| `rdrop` | `( x -- )` | drop TOS from r-stack | +| `rdrop2` | `( x y -- )` | remove top 2 vals from r-stack | +| `rswap` | `( x y -- y x )` | swap top 2 vals on r-stack | +| `rswap2` | `( a b c d -- c d a b )` | swap top 2 pairs on r-stack | +| `rsp` | `( -- stack.length )` | push r-stack depth on d-stack | +| `movdr` | `( x -- )` (d-stack effect) | push d-stack TOS on r-stack | +| `movrd` | `( -- x )` (d-stack effect) | push r-stack TOS on d-stack | +| `cpdr` | `( x -- x )` (d-stack effect) | copy d-stack TOS on r-stack | +| `cprd` | `( -- x )` (d-stack effect) | copy r-stack TOS on d-stack | ### Word & quotation execution / combinators -| Word | Stack effect | Description | -| --- | --- | --- | -| `exec` | `( w -- ? )` | call TOS as (compiled) word w/ curr ctx | -| `dip` | `( x q -- .. x )` | | -| `dip2` | `( x y q -- .. x y )` | | -| `dip3` | `( x y z q -- .. x y z )` | | -| `dip4` | `( x y z w q -- .. x y z w )` | | -| `keep` | `( x q -- .. x )` | | -| `keep2` | `( x y q -- .. x y )` | | -| `keep3` | `( x y z q -- .. x y z )` | | -| `bi` | `( x p q -- pres qres )` | | -| `bi2` | `( x y p q -- pres qres )` | | -| `bi3` | `( x y z p q -- pres qres )` | | +| Word | Stack effect | Description | +|---------|-------------------------------|-----------------------------------------| +| `exec` | `( w -- ? )` | call TOS as (compiled) word w/ curr ctx | +| `dip` | `( x q -- .. x )` | | +| `dip2` | `( x y q -- .. x y )` | | +| `dip3` | `( x y z q -- .. x y z )` | | +| `dip4` | `( x y z w q -- .. x y z w )` | | +| `keep` | `( x q -- .. x )` | | +| `keep2` | `( x y q -- .. x y )` | | +| `keep3` | `( x y z q -- .. x y z )` | | +| `bi` | `( x p q -- pres qres )` | | +| `bi2` | `( x y p q -- pres qres )` | | +| `bi3` | `( x y z p q -- pres qres )` | | ### Primitive math -| Word | Stack effect | Description | -| --- | --- | --- | -| `add` | `( x y -- x+y )` | -| `sub` | `( x y -- x-y )` | -| `mul` | `( x y -- x*y )` | -| `div` | `( x y -- x/y )` | -| `mod` | `( x y -- x%y )` | -| `inc` | `( x -- x+1 )` | -| `dec` | `( x -- x-1 )` | -| `neg` | `( x -- -x )` | -| `even` | `( x -- bool )` | true, if `x` is even | -| `odd` | `( x -- bool )` | true, if `x` is odd | -| `min` | `( x y -- min(x, y) )` | -| `max` | `( x y -- max(x, y) )` | -| `log` | `( x -- log(x) )` | -| `pow` | `( x y -- pow(x, y) )` | -| `rand` | `( -- Math.random() )` | -| `sqrt` | `( x -- sqrt(x) )` | -| `sin` | `( x -- sin(x) )` | -| `cos` | `( x -- cos(x) )` | -| `atan2` | `( x y -- atan2(y, x) )` | -| `lsl` | `( x y -- x<>y )` | -| `lsru` | `( x y -- x>>>y )` | -| `bitand` | `( x y -- x&y )` | -| `bitor` | `( x y -- x\|y )` | -| `bitxor` | `( x y -- x^y )` | -| `bitnot` | `( x -- ~x )` | +| Word | Stack effect | Description | +|----------|--------------------------|----------------------| +| `add` | `( x y -- x+y )` | | +| `sub` | `( x y -- x-y )` | | +| `mul` | `( x y -- x*y )` | | +| `div` | `( x y -- x/y )` | | +| `mod` | `( x y -- x%y )` | | +| `inc` | `( x -- x+1 )` | | +| `dec` | `( x -- x-1 )` | | +| `neg` | `( x -- -x )` | | +| `even` | `( x -- bool )` | true, if `x` is even | +| `odd` | `( x -- bool )` | true, if `x` is odd | +| `min` | `( x y -- min(x, y) )` | | +| `max` | `( x y -- max(x, y) )` | | +| `log` | `( x -- log(x) )` | | +| `pow` | `( x y -- pow(x, y) )` | | +| `rand` | `( -- Math.random() )` | | +| `sqrt` | `( x -- sqrt(x) )` | | +| `sin` | `( x -- sin(x) )` | | +| `cos` | `( x -- cos(x) )` | | +| `atan2` | `( x y -- atan2(y, x) )` | | +| `lsl` | `( x y -- x<>y )` | | +| `lsru` | `( x y -- x>>>y )` | | +| `bitand` | `( x y -- x&y )` | | +| `bitor` | `( x y -- x\|y )` | | +| `bitxor` | `( x y -- x^y )` | | +| `bitnot` | `( x -- ~x )` | | ### Logic -| Word | Stack effect | -| --- | --- | -| `eq` | `( x y -- x===y )` | -| `equiv` | `( x y -- equiv(x,y) )` | -| `neq` | `( x y -- x!==y )` | -| `and` | `( x y -- x&&y )` | -| `or` | `( x y -- x\|\|y )` | -| `not` | `( x -- !x )` | -| `lt` | `( x y -- xy )` | -| `lteq` | `( x y -- x<=y )` | -| `gteq` | `( x y -- x>=y )` | -| `iszero` | `( x -- x===0 )` | -| `ispos` | `( x -- x>0 )` | -| `isneg` | `( x -- x<0 )` | -| `isnull` | `( x -- x==null )` | +| Word | Stack effect | +|----------|-------------------------| +| `eq` | `( x y -- x===y )` | +| `equiv` | `( x y -- equiv(x,y) )` | +| `neq` | `( x y -- x!==y )` | +| `and` | `( x y -- x&&y )` | +| `or` | `( x y -- x\|\|y )` | +| `not` | `( x -- !x )` | +| `lt` | `( x y -- xy )` | +| `lteq` | `( x y -- x<=y )` | +| `gteq` | `( x y -- x>=y )` | +| `iszero` | `( x -- x===0 )` | +| `ispos` | `( x -- x>0 )` | +| `isneg` | `( x -- x<0 )` | +| `isnull` | `( x -- x==null )` | ### Environment -| Word | Stack effect | Description | -| --- | --- | --- | -| `load` | `( k -- env[k] )` | pushes `env[k]` on d-stack | -| `store` | `( x k -- )` | stores TOS as `env[k]` | -| `loadkey(k)` | `( -- env[k] )` | like `load` w/ predefined key | -| `storekey(k)` | `( x -- )` | like `store` w/ predefined key | -| `pushenv` | `( -- env )` | pushes curr env on d-stack | +| Word | Stack effect | Description | +|---------------|-------------------|--------------------------------| +| `load` | `( k -- env[k] )` | pushes `env[k]` on d-stack | +| `store` | `( x k -- )` | stores TOS as `env[k]` | +| `loadkey(k)` | `( -- env[k] )` | like `load` w/ predefined key | +| `storekey(k)` | `( x -- )` | like `store` w/ predefined key | +| `pushenv` | `( -- env )` | pushes curr env on d-stack | ### Arrays, objects, strings -| Word | Stack effect | Description | -| --- | --- | --- | -| `at` | `( obj k -- obj[k] )` | `obj` can be array/obj/string | -| `bindkeys` | `(v1 v2 .. [k1 k2 ..] obj -- obj )` | bind key/value pairs in `obj` | -| `collect` | `( ... n -- [...] )` | tuple of top `n` vals | -| `foldl` | `( arr q init -- x )` | like `mapl`, but w/ `init` val for reduction | -| `length` | `( x -- x.length )` | length of arraylike | -| `list` | `( -- [] )` | create new empty array | -| `mapl` | `( arr q -- ? )` | transform array w/ quotation (no explicit result array) | -| `mapll` | `( arr q -- ? )` | transform array w/ quotation | -| `obj` | `( -- {} )` | create new empty object | -| `pushl` | `( x arr -- arr )` | push `x` on LHS of array | -| `pushr` | `( arr x -- arr )` | push `x` on RHS of array | -| `popr` | `( arr -- arr arr[-1] )` | extract RHS of array as new TOS | -| `pull` | `( arr -- x arr )` | short for: `[popr, swap]` | -| `pull2` | `( arr -- x y arr )` | short for: `[pull, pull]` | -| `pull3` | `( arr -- x y z arr )` | short for: `[pull2, pull]` | -| `pull4` | `( arr -- a b c d arr )` | short for: `[pull2, pull2]` | -| `split` | `( arr x -- [...] [...] )` | split array at index `x` | -| `setat` | `( val obj k -- obj )` | `obj` can be array/obj | -| `tuple(n)` | `( ... -- [...] )` | HOF, like `collect`, but w/ predefined size | -| `vec2` | `( x y -- [x, y] )` | same as `tuple(2)` | -| `vec3` | `( x y z -- [x, y, z] )` | same as `tuple(3)` | -| `vec4` | `( x y z w -- [x, y, z, w] )` | same as `tuple(4)` | -| `vadd` | `( a b -- c )` | add 2 arrays (or array + scalar) | -| `vsub` | `( a b -- c )` | subtract 2 arrays (or array + scalar) | -| `vmul` | `( a b -- c )` | multiply 2 arrays (or array + scalar) | -| `vdiv` | `( a b -- c )` | divide 2 arrays (or array + scalar) | -| `op2v(f)` | `( a b -- c )` | HOF word gen, e.g. `vadd` is based on | +| Word | Stack effect | Description | +|------------|-------------------------------------|---------------------------------------------------------| +| `at` | `( obj k -- obj[k] )` | `obj` can be array/obj/string | +| `bindkeys` | `(v1 v2 .. [k1 k2 ..] obj -- obj )` | bind key/value pairs in `obj` | +| `collect` | `( ... n -- [...] )` | tuple of top `n` vals | +| `foldl` | `( arr q init -- x )` | like `mapl`, but w/ `init` val for reduction | +| `length` | `( x -- x.length )` | length of arraylike | +| `list` | `( -- [] )` | create new empty array | +| `mapl` | `( arr q -- ? )` | transform array w/ quotation (no explicit result array) | +| `mapll` | `( arr q -- ? )` | transform array w/ quotation | +| `obj` | `( -- {} )` | create new empty object | +| `pushl` | `( x arr -- arr )` | push `x` on LHS of array | +| `pushr` | `( arr x -- arr )` | push `x` on RHS of array | +| `popr` | `( arr -- arr arr[-1] )` | extract RHS of array as new TOS | +| `pull` | `( arr -- x arr )` | short for: `[popr, swap]` | +| `pull2` | `( arr -- x y arr )` | short for: `[pull, pull]` | +| `pull3` | `( arr -- x y z arr )` | short for: `[pull2, pull]` | +| `pull4` | `( arr -- a b c d arr )` | short for: `[pull2, pull2]` | +| `split` | `( arr x -- [...] [...] )` | split array at index `x` | +| `setat` | `( val obj k -- obj )` | `obj` can be array/obj | +| `tuple(n)` | `( ... -- [...] )` | HOF, like `collect`, but w/ predefined size | +| `vec2` | `( x y -- [x, y] )` | same as `tuple(2)` | +| `vec3` | `( x y z -- [x, y, z] )` | same as `tuple(3)` | +| `vec4` | `( x y z w -- [x, y, z, w] )` | same as `tuple(4)` | +| `vadd` | `( a b -- c )` | add 2 arrays (or array + scalar) | +| `vsub` | `( a b -- c )` | subtract 2 arrays (or array + scalar) | +| `vmul` | `( a b -- c )` | multiply 2 arrays (or array + scalar) | +| `vdiv` | `( a b -- c )` | divide 2 arrays (or array + scalar) | +| `op2v(f)` | `( a b -- c )` | HOF word gen, e.g. `vadd` is based on | ### I/O -| Word | Stack effect | Description | -| --- | --- | --- | -| `print` | `( x -- )` | `console.log(x)` | -| `printds` | `( -- )` | print out D-stack | -| `printrs` | `( -- )` | print out R-stack | +| Word | Stack effect | Description | +|-----------|--------------|-------------------| +| `print` | `( x -- )` | `console.log(x)` | +| `printds` | `( -- )` | print out D-stack | +| `printrs` | `( -- )` | print out R-stack | ### Control flow @@ -955,7 +955,7 @@ Non-HOF version of `cond`, expects `test` result and both branches on d-stack. Executes `thenq` word/quotation if `test` is truthy, else runs `elseq`. -``` +```forth ( test thenq elseq -- ? ) ``` @@ -980,13 +980,13 @@ executes body. Repeats while test is truthy. Non-HOF version of `loop`. Expects test result and body quotation/word on d-stack. -``` +```forth ( testq bodyq -- ? ) ``` #### `dotimes` -``` +```forth ( n body -- ? ) ``` diff --git a/packages/pointfree/package.json b/packages/pointfree/package.json index 455dc4ca5e..851fc280e8 100644 --- a/packages/pointfree/package.json +++ b/packages/pointfree/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/pointfree", - "version": "1.1.2", + "version": "1.2.2", "description": "Pointfree functional composition / Forth style stack execution engine", "module": "./index.js", "main": "./lib/index.js", @@ -29,15 +29,15 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/compose": "^1.3.2", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/compose": "^1.3.5", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1" }, "keywords": [ "composition", diff --git a/packages/pointfree/src/array.ts b/packages/pointfree/src/array.ts new file mode 100644 index 0000000000..d176c1d01c --- /dev/null +++ b/packages/pointfree/src/array.ts @@ -0,0 +1,382 @@ +import { isArray, isPlainObject } from "@thi.ng/checks"; +import { illegalArgs, illegalState } from "@thi.ng/errors"; +import { StackContext, StackFn } from "./api"; +import { op1, op2, op2v } from "./ops"; +import { $, $n } from "./safe"; +import { invrot, swap } from "./stack"; +import { $stackFn, word } from "./word"; + +//////////////////// Array / list ops //////////////////// + +/** + * Pushes a new empty array on the d-stack. While it's easily possible to + * use `[]` as part of a stack program, the `list` word is intended to + * be used as part of re-usuable `word()` definitions to ensure a new + * array is being created for every single invocation of the word (else + * only a single instance is created due to the mutable nature of JS + * arrays). + * + * Compare: + * + * ``` + * // using array literal within word definition + * foo = pf.word([ [], 1, pf.pushl ]) + * pf.runU(foo) + * // [ 1 ] + * pf.runU(foo) + * // [ 1, 1 ] // wrong! + * + * // using `list` instead + * bar = pf.word([ pf.list, 1, pf.pushl ]) + * pf.runU(bar) + * // [ 1 ] + * pf.runU(bar) + * // [ 1 ] // correct! + * ``` + * + * ( -- [] ) + * + * @param ctx + */ +export const list = (ctx: StackContext) => (ctx[0].push([]), ctx); + +/** + * Pushes new empty JS object on d-stack. + * Same reasoning as for `list`. + * + * ( -- {} ) + * + * @param ctx + */ +export const obj = (ctx: StackContext) => (ctx[0].push({}), ctx); + +/** + * Pushes `val` on the LHS of array. + * + * ( val arr -- arr ) + * + * @param ctx + */ +export const pushl = (ctx: StackContext) => { + $(ctx[0], 2); + const stack = ctx[0]; + const a = stack.pop(); + a.unshift(stack.pop()); + stack.push(a); + return ctx; +}; + +/** + * Pushes `val` on the RHS of array. + * + * ( arr val -- arr ) + * + * @param ctx + */ +export const pushr = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 2; + $n(n, 0); + stack[n].push(stack[n + 1]); + stack.length--; + return ctx; +}; + +/** + * Removes RHS from array as new TOS on d-stack. + * Throws error is `arr` is empty. + * + * ( arr -- arr arr[-1] ) + * + * @param ctx + */ +export const popr = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 1; + $n(n, 0); + const a = stack[n]; + !a.length && illegalState("can't pop empty array"); + stack.push(a.pop()); + return ctx; +}; + +export const pull = word([popr, swap]); +export const pull2 = word([pull, pull]); +export const pull3 = word([pull2, pull]); +export const pull4 = word([pull2, pull2]); + +export const vadd = op2v((b, a) => a + b); +export const vsub = op2v((b, a) => a - b); +export const vmul = op2v((b, a) => a * b); +export const vdiv = op2v((b, a) => a / b); + +/** + * Splits vector / array at given index `x`. + * + * ( arr x -- [...] [...] ) + * + * @param ctx + */ +export const split = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 2; + $n(n, 0); + const a = stack[n]; + const b = stack[n + 1]; + stack[n + 1] = a.splice(b, a.length - b); + return ctx; +}; + +/** + * Concatenates two arrays on d-stack: + * + * ( arr1 arr2 -- arr ) + * + * @param ctx + */ +export const cat = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 2; + $n(n, 0); + stack[n] = stack[n].concat(stack.pop()); + return ctx; +}; + +/** + * Generic array transformer. + * + * ( arr q -- ? ) + * + * Pops both args from d-stack, then executes quotation for each array + * item (each pushed on d-stack prior to calling quotation). Can produce + * any number of results and therefore also be used as filter, mapcat, + * reduce... + * + * ``` + * // each item times 10 + * run([[1, 2, 3, 4], [10, mul], mapl]) + * // [ [ 10, 20, 30, 40 ], [], {} ] + * ``` + * + * Use for filtering: + * + * ``` + * // drop even numbers, duplicate odd ones + * run([[1, 2, 3, 4], [dup, even, cond(drop, dup)], mapl]) + * // [ [ 1, 1, 3, 3 ], [], {} ] + * ``` + * + * Reduction: + * + * ``` + * // the `0` is the initial reduction result + * runU([0, [1, 2, 3, 4], [add], mapl]) + * // 10 + * ``` + * + * **Important**: `mapl` does not produce a result array. However, + * there're several options to collect results as array, e.g. + * + * Use `mapll()` to transform: + * + * ``` + * runU([[1, 2, 3, 4], [10, mul], mapll]) + * // [ 10, 20, 30, 40] + * ``` + * + * Collecting results as array is a form of reduction, so we can use + * `list` to produce an initial new array and `pushr` to push each new + * interim value into the result: + * + * ``` + * runU([list, [1, 2, 3, 4], [10, mul, pushr], mapl]) + * // [ 10, 20, 30, 40 ] + * ``` + * + * If the array size is known & not changed by transformation: + * + * ``` + * runU([[1, 2, 3, 4], [10, mul], mapl, 4, collect]) + * // [ 10, 20, 30, 40 ] + * ``` + * + * @param ctx + */ +export const mapl = (ctx: StackContext) => { + $(ctx[0], 2); + const stack = ctx[0]; + const w = $stackFn(stack.pop()); + const list = stack.pop(); + const n = list.length; + for (let i = 0; i < n; i++) { + ctx[0].push(list[i]); + ctx = w(ctx); + } + return ctx; +}; + +/** + * Similar to `mapl()`, but produces new array of transformed values. + * + * ( arr q -- arr ) + * + * ``` + * runU([[1, 2, 3, 4], [10, mul], mapll]) + * // [ 10, 20, 30, 40] + * ``` + * + * Filter / mapcat: + * + * ``` + * // drop even numbers, duplicate odd ones + * run([[1, 2, 3, 4], [dup, even, cond(drop, dup)], mapll]) + * // [ [ [ 1, 1, 3, 3 ] ], [], {} ] + * ``` + * + * @param ctx + */ +export const mapll = (ctx: StackContext) => { + $(ctx[0], 2); + let stack = ctx[0]; + const w = $stackFn(stack.pop()); + const list = stack.pop(); + const n = list.length; + let r = 0; + for (let i = 0; i < n; i++) { + let m = stack.length; + stack.push(list[i]); + ctx = w(ctx); + stack = ctx[0]; + r += stack.length - m; + } + stack.push(stack.splice(stack.length - r, r)); + return ctx; +}; + +/** + * Convenience wrapper for `mapl` to provide an alternative stack layout + * for reduction purposes: + * + * ( arr q init -- reduction ) + */ +export const foldl = word([invrot, mapl]); + +/** + * Pops TOS (a number) and then forms a tuple of the top `n` remaining + * values and pushes it as new TOS. The original collected stack values + * are removed from d-stack. + * + * ( ... n --- ... [...] ) + * + * @param ctx + */ +export const collect = (ctx: StackContext) => { + const stack = ctx[0]; + let n = stack.length - 1, + m; + $n(n, 0); + $n((n -= m = stack.pop()), 0); + stack.push(stack.splice(n, m)); + return ctx; +}; + +/** + * Higher order helper word to `collect()` tuples of pre-defined size + * `n`. The size can be given as number or a stack function producing a + * number. + * + * ( ... -- [...]) + * + * @param n + */ +export const tuple = (n: number | StackFn) => word([n, collect]); + +export const vec2 = tuple(2); +export const vec3 = tuple(3); +export const vec4 = tuple(4); + +/** + * Higher order helper word to convert a TOS tuple/array into a string + * using `Array.join()` with given `sep`arator. + * + * @param sep + */ +export const join = (sep = "") => op1((x) => x.join(sep)); + +/** + * Pushes length of TOS on d-stack. + * + * ( x -- x.length ) + * + * @param ctx + */ +export const length = op1((x) => x.length); + +/** + * Replaces TOS with its shallow copy. MUST be an array or plain object. + * + * ( x -- copy ) + */ +export const copy = op1((x) => + isArray(x) + ? x.slice() + : isPlainObject(x) + ? { ...x } + : illegalArgs(`can't copy type ${typeof x}`) +); + +/** + * Reads key/index from object/array. + * + * ( obj k -- obj[k] ) + * + * @param ctx + */ +export const at = op2((b, a) => a[b]); + +/** + * Writes `val` at key/index in object/array. + * + * ( val obj k -- obj ) + * + * @param ctx + */ +export const setat = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 3; + $n(n, 0); + stack[n + 1][stack[n + 2]] = stack[n]; + stack[n] = stack[n + 1]; + stack.length -= 2; + return ctx; +}; + +//////////////////// Objects //////////////////// + +/** + * Takes an array of keys and target object, then pops & binds deeper + * stack values to respective keys in object. Pushes result object back + * on stack at the end. Throws error if there're less stack values than + * keys in given array. + * + * ``` + * runU([1,2,3, ["a","b","c"], {}, bindkeys]) + * // { c: 3, b: 2, a: 1 } + * ``` + * + * (v1 v2 .. [k1 k2 ..] obj -- obj ) + * + * @param ctx + */ +export const bindkeys = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 2); + const obj = stack.pop(); + const keys = stack.pop(); + $(stack, keys.length); + for (let i = keys.length - 1; i >= 0; i--) { + obj[keys[i]] = stack.pop(); + } + stack.push(obj); + return ctx; +}; diff --git a/packages/pointfree/src/binary.ts b/packages/pointfree/src/binary.ts new file mode 100644 index 0000000000..00b27c3a4c --- /dev/null +++ b/packages/pointfree/src/binary.ts @@ -0,0 +1,52 @@ +import { op1, op2 } from "./ops"; + +//////////////////// Binary ops //////////////////// + +/** + * ( x y -- x&y ) + * + * @param ctx + */ +export const bitand = op2((b, a) => a & b); + +/** + * ( x y -- x|y ) + * + * @param ctx + */ +export const bitor = op2((b, a) => a | b); + +/** + * ( x y -- x^y ) + * + * @param ctx + */ +export const bitxor = op2((b, a) => a ^ b); + +/** + * ( x -- ~x ) + * + * @param ctx + */ +export const bitnot = op1((x) => ~x); + +/** + * ( x y -- x< a << b); + +/** + * ( x y -- x>>y ) + * + * @param ctx + */ +export const lsr = op2((b, a) => a >> b); + +/** + * ( x y -- x>>>y ) + * + * @param ctx + */ +export const lsru = op2((b, a) => a >>> b); diff --git a/packages/pointfree/src/cond.ts b/packages/pointfree/src/cond.ts new file mode 100644 index 0000000000..bf6d5102b2 --- /dev/null +++ b/packages/pointfree/src/cond.ts @@ -0,0 +1,76 @@ +import { IObjectOf } from "@thi.ng/api"; +import { illegalState } from "@thi.ng/errors"; +import { StackContext, StackProc } from "./api"; +import { $ } from "./safe"; +import { nop } from "./stack"; +import { $stackFn } from "./word"; + +//////////////////// Conditionals //////////////////// + +/** + * Higher order word. Takes two stack programs: truthy and falsey + * branches, respectively. When executed, pops TOS and runs only one of + * the branches depending if TOS was truthy or not. + * + * Note: Unlike JS `if() {...} else {...}` constructs, the actual + * conditional is NOT part of this word. + * + * ( bool -- ? ) + * + * @param _then + * @param _else + */ +export const cond = (_then: StackProc, _else: StackProc = nop) => ( + ctx: StackContext +) => ($(ctx[0], 1), $stackFn(ctx[0].pop() ? _then : _else)(ctx)); + +/** + * Non-HOF version of `cond`, expects `test` result and both branches on + * d-stack. Executes `thenq` word/quotation if `test` is truthy, else + * runs `elseq`. + * + * ( test thenq elseq -- ? ) + * + * @param ctx + */ +export const condq = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 3); + const _else = stack.pop(); + const _then = stack.pop(); + return $stackFn(stack.pop() ? _then : _else)(ctx); +}; + +/** + * Higher order word. Takes an object of stack programs with keys in the + * object being used to check for equality with TOS. If a match is + * found, executes corresponding stack program. If a `default` key is + * specified and no other cases matched, run `default` program. In all + * other cases throws an error. + * + * Important: The default case has the original TOS re-added to the + * d-stack before execution. + * + * @param cases + */ +export const cases = (cases: IObjectOf) => (ctx: StackContext) => { + $(ctx[0], 1); + const stack = ctx[0]; + const tos = stack.pop(); + const cas = cases[tos]; + if (cas !== undefined) { + return $stackFn(cas)(ctx); + } + if (cases.default) { + stack.push(tos); + return $stackFn(cases.default)(ctx); + } + illegalState(`no matching case for: ${tos}`); + return ctx; +}; + +export const casesq = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 2); + return cases(stack.pop())(ctx); +}; diff --git a/packages/pointfree/src/context.ts b/packages/pointfree/src/context.ts new file mode 100644 index 0000000000..82c2dd6095 --- /dev/null +++ b/packages/pointfree/src/context.ts @@ -0,0 +1,14 @@ +import { Stack, StackContext, StackEnv } from "./api"; + +/** + * Creates a new StackContext tuple from given d-stack and/or + * environment only (the r-stack is always initialized empty). + * + * @param stack initial d-stack + * @param env initial environment + */ +export const ctx = (stack: Stack = [], env: StackEnv = {}): StackContext => [ + stack, + [], + env +]; diff --git a/packages/pointfree/src/dataflow.ts b/packages/pointfree/src/dataflow.ts new file mode 100644 index 0000000000..ce1ad4078b --- /dev/null +++ b/packages/pointfree/src/dataflow.ts @@ -0,0 +1,206 @@ +import { StackContext } from "./api"; +import { and, or } from "./logic"; +import { $ } from "./safe"; +import { + dup, + dup2, + dup3, + over, + swap +} from "./stack"; +import { $stackFn, exec, word } from "./word"; + +//////////////////// Dataflow combinators //////////////////// + +// these combinators have been ported from Factor: +// http://docs.factorcode.org:8080/content/article-dataflow-combinators.html + +/** + * Removes `x` from d-stack, calls `q` and restores `x` to the top of + * the d-stack after quotation is finished. + * + * ( x q -- x ) + * + * Same behavior as: `[swap, movdr, exec, movrd]`, only the current + * implementation doesn't use r-stack and stashes `x` off stack. + * + * @param ctx + */ +export const dip = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 2); + const q = stack.pop(); + const x = stack.pop(); + ctx = $stackFn(q)(ctx); + ctx[0].push(x); + return ctx; +}; + +/** + * Removes `x y` from d-stack, calls `q` and restores removed vals + * to the top of the d-stack after quotation is finished. + * + * ( x y q -- x y ) + */ +export const dip2 = word([swap, [dip], dip]); + +/** + * Removes `x y z` from d-stack, calls `q` and restores removed + * vals to the top of the d-stack after quotation is finished. + * + * ( x y z q -- x y z ) + */ +export const dip3 = word([swap, [dip2], dip]); + +/** + * Removes `x y z w` from d-stack, calls `q` and restores removed + * vals to the top of the d-stack after quotation is finished. + * + * ( x y z w q -- x y z w ) + */ +export const dip4 = word([swap, [dip3], dip]); + +/** + * Calls a quotation with a value on the d-stack, restoring the value + * after quotation finished. + * + * ( x q -- .. x ) + */ +export const keep = word([over, [exec], dip]); + +/** + * Call a quotation with two values on the stack, restoring the values + * after quotation finished. + * + * ( x y q -- .. x y ) + */ +export const keep2 = word([[dup2], dip, dip2]); + +/** + * Call a quotation with three values on the stack, restoring the values + * after quotation finished. + * + * ( x y z q -- .. x y z ) + */ +export const keep3 = word([[dup3], dip, dip3]); + +/** + * First applies `p` to the value `x`, then applies `q` to the same + * value. + * + * ( x p q -- px qx ) + */ +export const bi = word([[keep], dip, exec]); + +/** + * First applies `p` to the two input values `x y`, then applies `q` to + * the same values. + * + * ( x y p q -- pxy qxy ) + */ +export const bi2 = word([[keep2], dip, exec]); + +/** + * First applies `p` to the three input values `x y z`, then applies `q` + * to the same values. + * + * ( x y z p q -- pxyz qxyz ) + */ +export const bi3 = word([[keep3], dip, exec]); + +/** + * Applies `p` to `x`, then `q` to `x`, and finally `r` to `x` + * + * ( x p q r -- px qx rx ) + */ +export const tri = word([[[keep], dip, keep], dip, exec]); + +/** + * Applies `p` to the two input values `x y`, then same with `q`, and + * finally with `r`. + * + * ( x y p q r -- pxy qxy rxy ) + */ +export const tri2 = word([[[keep2], dip, keep2], dip, exec]); + +/** + * Applies `p` to the three input values `x y z`, then same with `q`, + * and finally with `r`. + * + * ( x y z p q r -- pxyz qxyz rxyz ) + */ +export const tri3 = word([[[keep3], dip, keep3], dip, exec]); + +/** + * Applies `p` to `x`, then applies `q` to `y`. + * + * ( x y p q -- px qy ) + */ +export const bis = word([[dip], dip, exec]); + +/** + * Applies `p` to `a b`, then applies `q` to `c d`. + * + * ( a b c d p q -- pab qcd ) + */ +export const bis2 = word([[dip2], dip, exec]); + +/** + * Applies `p` to `x`, then `q` to `y`, and finally `r` to `z`. + * + * ( x y z p q r -- ) + */ +export const tris = word([[[dip2], dip, dip], dip, exec]); + +/** + * Applies `p` to `u v`, then `q` to `w x`, and finally `r` to `y z`. + * + * ( u v w x y z p q r -- puv qwx ryz ) + */ +export const tris2 = word([[dip4], dip2, bis2]); + +/** + * Applies the quotation `q` to `x`, then to `y`. + * + * ( x y q -- qx qy ) + */ +export const bia = word([dup, bis]); + +/** + * Applies the quotation `q` to `x y`, then to `z w`. + * + * ( x y z w q -- qxy qzw ) + */ +export const bia2 = word([dup, bis2]); + +/** + * Applies the `q` to `x`, then to `y`, and finally to `z`. + * + * ( x y z q -- qx qy qz ) + */ +export const tria = word([dup, dup, tris]); + +/** + * Applies the quotation to `u v`, then to `w x`, and then to `y z`. + * + * ( u v w x y z q -- quv qwx qyz ) + */ +export const tria2 = word([dup, dup, tris2]); + +/** + * Applies `q` individually to both input vals `x y` and combines + * results with `and`. The final result will be true if both interim + * results were truthy. + * + * ( x y q -- qx && qy ) + */ +export const both = word([bia, and]); + +/** + * Applies `q` individually to both input vals `x y` and combines results with `or`. + * The final result will be true if at least one of the interim results + * was truthy. + * + * ( x y q -- qx || qy ) + */ +export const either = word([bia, or]); diff --git a/packages/pointfree/src/env.ts b/packages/pointfree/src/env.ts new file mode 100644 index 0000000000..a4b9862cb3 --- /dev/null +++ b/packages/pointfree/src/env.ts @@ -0,0 +1,75 @@ +import { illegalArgs } from "@thi.ng/errors"; +import { StackContext } from "./api"; +import { $ } from "./safe"; + +//////////////////// Environment //////////////////// + +/** + * Pushes current env onto d-stack. + * + * ( -- env ) + * + * @param ctx + * @param env + */ +export const pushenv = (ctx: StackContext) => (ctx[0].push(ctx[2]), ctx); + +/** + * Loads value for `key` from current env and pushes it on d-stack. + * Throws error if var doesn't exist. + * + * ( key -- env[key] ) + * + * @param ctx + * @param env + */ +export const load = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 1); + const id = stack.pop(); + !ctx[2].hasOwnProperty(id) && illegalArgs(`unknown var: ${id}`); + stack.push(ctx[2][id]); + return ctx; +}; + +/** + * Stores `val` under `key` in env. + * + * ( val key -- ) + * + * @param ctx + * @param env + */ +export const store = (ctx: StackContext) => ( + $(ctx[0], 2), (ctx[2][ctx[0].pop()] = ctx[0].pop()), ctx +); + +/** + * Higher order word. Similar to `load`, but always uses given + * preconfigured `key` instead of reading it from d-stack at runtime + * (also slightly faster). Throws error if var doesn't exist. + * + * ( -- env[key] ) + * @param ctx + * @param env + */ +export const loadkey = (key: PropertyKey) => (ctx: StackContext) => { + !ctx[2].hasOwnProperty(key) && + illegalArgs(`unknown var: ${key.toString()}`); + ctx[0].push(ctx[2][key]); + return ctx; +}; + +/** + * Higher order word. Similar to `store`, but always uses given + * preconfigure `key` instead of reading it from d-stack at runtime + * (also slightly faster). + * + * ( val -- ) + * + * @param ctx + * @param env + */ +export const storekey = (key: PropertyKey) => (ctx: StackContext) => ( + $(ctx[0], 1), (ctx[2][key] = ctx[0].pop()), ctx +); diff --git a/packages/pointfree/src/index.ts b/packages/pointfree/src/index.ts index c85b6e7b67..83fdc48d02 100644 --- a/packages/pointfree/src/index.ts +++ b/packages/pointfree/src/index.ts @@ -1,1814 +1,19 @@ -import { - Fn, - Fn2, - IObjectOf, - NO_OP -} from "@thi.ng/api"; -import { isArray, isFunction, isPlainObject } from "@thi.ng/checks"; -import { compL } from "@thi.ng/compose"; import { equiv as _equiv } from "@thi.ng/equiv"; -import { illegalArgs, illegalState } from "@thi.ng/errors"; -import { - Stack, - StackContext, - StackEnv, - StackFn, - StackProc, - StackProgram -} from "./api"; - -let $: (stack: Stack, n: number) => void; -let $n: (m: number, n: number) => void; - -export const safeMode = (state: boolean) => { - if (state) { - $n = (m: number, n: number) => m < n && illegalState(`stack underflow`); - $ = (stack: Stack, n: number) => $n(stack.length, n); - } else { - $ = $n = NO_OP; - } -}; -safeMode(true); - -/** - * Executes program / quotation with given stack context (initial D/R - * stacks and optional environment). Returns updated context. - * - * @param prog - * @param ctx - */ -export const run = ( - prog: StackProc, - ctx: StackContext = [[], [], {}] -): StackContext => { - // !ctx[0] && (ctx[0] = []); - // !ctx[1] && (ctx[1] = []); - // !ctx[2] && (ctx[2] = {}); - if (isFunction(prog)) { - return prog(ctx); - } - for ( - let p = isArray(prog) ? prog : [prog], n = p.length, i = 0, w; - i < n; - i++ - ) { - if (isFunction((w = p[i]))) { - ctx = w(ctx); - } else { - ctx[0].push(w); - } - } - return ctx; -}; - -/** - * Like `run()`, but returns unwrapped result. Syntax sugar for: - * `unwrap(run(...),n)` - * - * @param prog - * @param ctx - * @param n - */ -export const runU = (prog: StackProc, ctx?: StackContext, n = 1) => - unwrap(run(prog, ctx), n); - -/** - * Like `run()`, but returns result environment. Syntax sugar for: - * `run(...)[2]` - * - * @param prog - * @param ctx - * @param n - */ -export const runE = (prog: StackProc, ctx?: StackContext) => run(prog, ctx)[2]; - -/** - * Creates a new StackContext tuple from given d-stack and/or - * environment only (the r-stack is always initialized empty). - * - * @param stack initial d-stack - * @param env initial environment - */ -export const ctx = (stack: Stack = [], env: StackEnv = {}): StackContext => [ - stack, - [], - env -]; - -const $stackFn = (f: StackProc) => (isArray(f) ? word(f) : f); - -const tos = (stack: Stack) => stack[stack.length - 1]; - -const compile = (prog: StackProgram) => - compL.apply(null, ( - prog.map((w) => - !isFunction(w) ? (ctx: StackContext) => (ctx[0].push(w), ctx) : w - ) - )); - -/** - * Takes a result tuple returned by `run()` and unwraps one or more - * items from result stack. If no `n` is given, defaults to single value - * (TOS) and returns it as is. Returns an array for all other `n`. - * - * @param result - * @param n - */ -export const unwrap = ([stack]: StackContext, n = 1) => - n === 1 ? tos(stack) : stack.slice(Math.max(0, stack.length - n)); - -//////////////////// Dynamic words & quotations //////////////////// - -/** - * Higher order word. Takes a StackProgram and returns it as StackFn to - * be used like any word. Unknown stack effect. - * - * If the optional `env` is given, uses a shallow copy of that - * environment (one per invocation) instead of the current one passed by - * `run()` at runtime. If `mergeEnv` is true (default), the user - * provided env will be merged with the current env (also shallow - * copies). This is useful in conjunction with `pushenv()` and `store()` - * or `storekey()` to save results of sub procedures in the main env. - * - * Note: The provided (or merged) env is only active within the - * execution scope of the word. - * - * ( ? -- ? ) - * - * @param prog - * @param env - * @param mergeEnv - */ -export const word = (prog: StackProgram, env?: StackEnv, mergeEnv = true) => { - const w: StackFn = compile(prog); - return env - ? mergeEnv - ? (ctx: StackContext) => ( - w([ctx[0], ctx[1], { ...ctx[2], ...env }]), ctx - ) - : (ctx: StackContext) => (w([ctx[0], ctx[1], { ...env }]), ctx) - : w; -}; - -/** - * Like `word()`, but automatically calls `unwrap()` on result context - * to produced unwrapped value/tuple. - * - * **Importatant:** Words defined with this function CANNOT be used as - * part of a larger stack program, only for standalone use. - * - * @param prog - * @param n - * @param env - * @param mergeEnv - */ -export const wordU = ( - prog: StackProgram, - n = 1, - env?: StackEnv, - mergeEnv = true -) => { - const w: StackFn = compile(prog); - return env - ? mergeEnv - ? (ctx: StackContext) => - unwrap(w([ctx[0], ctx[1], { ...ctx[2], ...env }]), n) - : (ctx: StackContext) => unwrap(w([ctx[0], ctx[1], { ...env }]), n) - : (ctx: StackContext) => unwrap(w(ctx), n); -}; - -/** - * Executes TOS as stack function and places result back on d-stack. TOS - * MUST be a valid word or quotation. - * - * ( x -- x() ) - * - * @param ctx - */ -export const exec = (ctx: StackContext) => ( - $(ctx[0], 1), $stackFn(ctx[0].pop())(ctx) -); - -//////////////////// JS host calls //////////////////// - -/** - * Expects TOS to be a quotation with a vanilla JS function as first - * element. Calls fn with all remaining items in quot as arguments and - * pushes result back on d-stack (even if fn returned `undefined`). - * - * ( [f ...] -- f(...) ) - * - * @param ctx - */ -export const execjs = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 1); - const [fn, ...args] = stack.pop(); - stack.push(fn(...args)); - return ctx; -}; - -//////////////////// Operator generators //////////////////// - -/** - * Higher order word. Replaces TOS of d-stack with result of given op. - * - * ( x -- y ) - * - * @param op - */ -const op1 = (op: Fn) => { - return (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 1; - $n(n, 0); - stack[n] = op(stack[n]); - return ctx; - }; -}; - -/** - * Higher order word. Takes 2 values from d-stack and writes back result - * from given op. The arg order is (TOS, TOS-1) - * - * ( a b -- c ) - * - * @param op - */ -const op2 = (op: Fn2) => (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 2; - $n(n, 0); - stack[n] = op(stack.pop(), stack[n]); - return ctx; -}; - -export { op1 as maptos, op2 as map2 }; - -/** - * Similar to `op2`, but for array operators. Either `a` or `b` can be a - * non-array value, but not both. Creates new array of result values. - * The result will have the same length as the shortest arg (if `a` and - * `b` have different lengths). - * - * - ( a b -- a ), if `a` is an array - * - ( a b -- b ), if `a` is not an array - * - * @param f - */ -export const op2v = (f: Fn2) => ( - ctx: StackContext -): StackContext => { - $(ctx[0], 2); - const stack = ctx[0]; - const b = stack.pop(); - const n = stack.length - 1; - const a = stack[n]; - const isa = isArray(a); - const isb = isArray(b); - let c: any[]; - if (isa && isb) { - c = new Array(Math.min(a.length, b.length)); - for (let i = c.length - 1; i >= 0; i--) { - c[i] = f(b[i], a[i]); - } - } else { - if (isb && !isa) { - c = new Array(b.length); - for (let i = c.length - 1; i >= 0; i--) { - c[i] = f(b[i], a); - } - } else if (isa && !isb) { - c = new Array(a.length); - for (let i = c.length - 1; i >= 0; i--) { - c[i] = f(b, a[i]); - } - } else { - illegalArgs("at least one arg must be an array"); - } - } - stack[n] = c!; - return ctx; -}; - -//////////////////// Stack manipulation words //////////////////// - -/** - * Utility word w/ no stack nor side effect. - */ -export const nop = (ctx: StackContext) => ctx; - -/** - * Pushes current d-stack size on d-stack. - * - * ( -- n ) - * @param ctx - */ -export const dsp = (ctx: StackContext) => (ctx[0].push(ctx[0].length), ctx); - -/** - * Uses TOS as index to look up a deeper d-stack value, then places it - * as new TOS. Throws error if stack depth is < `x`. - * - * ( ... x -- ... stack[x] ) - * - * @param ctx - */ -export const pick = (ctx: StackContext) => { - const stack = ctx[0]; - let n = stack.length - 1; - $n(n, 0); - $n((n -= stack.pop() + 1), 0); - stack.push(stack[n]); - return ctx; -}; - -/** - * Removes TOS from d-stack. - * - * ( x -- ) - * - * @param ctx - */ -export const drop = (ctx: StackContext) => ($(ctx[0], 1), ctx[0].length--, ctx); - -/** - * Removes top 2 vals from d-stack. - * - * ( x y -- ) - * - * @param ctx - */ -export const drop2 = (ctx: StackContext) => ( - $(ctx[0], 2), (ctx[0].length -= 2), ctx -); - -/** - * If TOS is truthy then drop it: - * - * ( x -- ) - * - * Else, no effect: - * - * ( x -- x ) - */ -export const dropif = (ctx: StackContext) => ( - $(ctx[0], 1), tos(ctx[0]) && ctx[0].length--, ctx -); - -/** - * Higher order word. Pushes given args verbatim on d-stack. - * - * ( -- ...args ) - * - * @param args - */ -export const push = (...args: any[]) => (ctx: StackContext) => ( - ctx[0].push(...args), ctx -); - -/** - * Duplicates TOS on d-stack. - * - * ( x -- x x ) - * - * @param ctx - */ -export const dup = (ctx: StackContext) => ( - $(ctx[0], 1), ctx[0].push(tos(ctx[0])), ctx -); - -/** - * Duplicates top 2 vals on d-stack. - * - * ( x y -- x y x y ) - * - * @param ctx - */ -export const dup2 = (ctx: StackContext) => { - const stack = ctx[0]; - let n = stack.length - 2; - $n(n, 0); - stack.push(stack[n], stack[n + 1]); - return ctx; -}; - -/** - * Duplicates top 3 vals on d-stack. - * - * ( x y -- x y x y ) - * - * @param ctx - */ -export const dup3 = (ctx: StackContext) => { - const stack = ctx[0]; - let n = stack.length - 3; - $n(n, 0); - stack.push(stack[n], stack[n + 1], stack[n + 2]); - return ctx; -}; - -/** - * If TOS is truthy then push copy of it on d-stack: - * - * ( x -- x x ) - * - * Else, no effect: - * - * ( x -- x ) - * - * @param ctx - */ -export const dupif = (ctx: StackContext) => { - $(ctx[0], 1); - const x = tos(ctx[0]); - x && ctx[0].push(x); - return ctx; -}; - -const _swap = (i: number) => (ctx: StackContext) => { - const stack = ctx[i]; - const n = stack.length - 2; - $n(n, 0); - const a = stack[n]; - stack[n] = stack[n + 1]; - stack[n + 1] = a; - return ctx; -}; - -const _swap2 = (i: number) => (ctx: StackContext) => { - const stack = ctx[i]; - let n = stack.length - 1; - $n(n, 3); - let a = stack[n]; - stack[n] = stack[n - 2]; - stack[n - 2] = a; - n--; - a = stack[n]; - stack[n] = stack[n - 2]; - stack[n - 2] = a; - return ctx; -}; - -/** - * Swaps the two topmost d-stack items. - * - * ( x y -- y x ) - * - * @param ctx - */ -export const swap = _swap(0); - -/** - * Swaps the two topmost d-stack pairs. - * - * ( a b c d -- c d a b ) - * - * @param ctx - */ -export const swap2 = _swap2(0); - -/** - * Removes second topmost item from d-stack. - * - * ( x y -- y ) - * - * @param ctx - */ -export const nip = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 2; - $n(n, 0); - stack[n] = stack.pop(); - return ctx; -}; - -/** - * Inserts copy of TOS @ TOS-2 in d-stack. - * - * ( x y -- y x y ) - * - * @param ctx - */ -export const tuck = (ctx: StackContext) => { - $(ctx[0], 2); - const stack = ctx[0]; - const a = stack.pop(); - stack.push(a, stack.pop(), a); - return ctx; -}; - -/** - * Rotates three topmost d-stack items downwards/to the left. - * - * ( x y z -- y z x ) - * - * @param ctx - */ -export const rot = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 1; - $n(n, 2); - const c = stack[n - 2]; - stack[n - 2] = stack[n - 1]; - stack[n - 1] = stack[n]; - stack[n] = c; - return ctx; -}; - -/** - * Rotates three topmost d-stack items upwards/to the right. - * - * ( x y z -- z x y ) - * - * @param ctx - */ -export const invrot = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 1; - $n(n, 2); - const c = stack[n]; - stack[n] = stack[n - 1]; - stack[n - 1] = stack[n - 2]; - stack[n - 2] = c; - return ctx; -}; - -/** - * Pushes copy of TOS-1 as new TOS on d-stack. - * - * ( x y -- x y x ) - * - * @param ctx - */ -export const over = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 2; - $n(n, 0); - stack.push(stack[n]); - return ctx; -}; - -//////////////////// R-Stack ops //////////////////// - -/** - * Pushes current r-stack size on d-stack. - * - * ( -- n ) - * - * @param ctx - */ -export const rsp = (ctx: StackContext) => (ctx[0].push(ctx[1].length), ctx); - -/** - * Removes TOS from r-stack. - * - * ( x -- ) - * - * @param ctx - */ -export const rdrop = (ctx: StackContext) => ( - $(ctx[1], 1), ctx[1].length--, ctx -); - -/** - * Removes top 2 vals from r-stack. - * - * ( x y -- ) - * - * @param ctx - */ -export const rdrop2 = (ctx: StackContext) => ( - $(ctx[1], 2), (ctx[1].length -= 2), ctx -); - -export const movdr = (ctx: StackContext) => ( - $(ctx[0], 1), ctx[1].push(ctx[0].pop()), ctx -); - -export const movrd = (ctx: StackContext) => ( - $(ctx[1], 1), ctx[0].push(ctx[1].pop()), ctx -); - -export const cpdr = (ctx: StackContext) => ( - $(ctx[0], 1), ctx[1].push(tos(ctx[0])), ctx -); - -export const cprd = (ctx: StackContext) => ( - $(ctx[1], 1), ctx[0].push(tos(ctx[1])), ctx -); - -const mov2 = (a: number, b: number) => (ctx: StackContext) => { - const src = ctx[a]; - $(src, 2); - const v = src.pop(); - ctx[b].push(src.pop(), v); - return ctx; -}; - -const cp2 = (a: number, b: number) => (ctx: StackContext) => { - const src = ctx[a]; - const n = src.length - 2; - $n(n, 0); - ctx[b].push(src[n], src[n + 1]); - return ctx; -}; - -export const movdr2 = mov2(0, 1); -export const movrd2 = mov2(1, 0); -export const cpdr2 = cp2(0, 1); -export const cprd2 = cp2(1, 0); - -/** - * Swaps the two topmost r-stack items. - * - * ( x y -- y x ) - * - * @param ctx - */ -export const rswap = _swap(1); - -/** - * Swaps the two topmost d-stack pairs. - * - * ( a b c d -- c d a b ) - * - * @param ctx - */ -export const rswap2 = _swap2(1); - -/** - * Like `inc`, but applies to r-stack TOS. - * - * @param ctx - */ -export const rinc = (ctx: StackContext) => ( - $(ctx[1], 1), ctx[1][ctx[1].length - 1]++, ctx -); - -/** - * Like `dec`, but applies to r-stack TOS. - * - * @param ctx - */ -export const rdec = (ctx: StackContext) => ( - $(ctx[1], 1), ctx[1][ctx[1].length - 1]--, ctx -); - -//////////////////// Math ops //////////////////// - -/** - * ( x y -- x+y ) - * - * @param ctx - */ -export const add = op2((b, a) => a + b); - -/** - * ( x y -- x*y ) - * - * @param ctx - */ -export const mul = op2((b, a) => a * b); - -/** - * ( x y -- x-y ) - * - * @param ctx - */ -export const sub = op2((b, a) => a - b); - -/** - * ( x y -- x/y ) - * - * @param ctx - */ -export const div = op2((b, a) => a / b); - -/** - * ( x -- 1/x ) - * - * @param ctx - */ -export const oneover = word([1, swap, div]); - -/** - * ( x y -- x%y ) - * - * @param ctx - */ -export const mod = op2((b, a) => a % b); - -/** - * ( x y -- min(x,y) ) - * - * @param ctx - */ -export const min = op2(Math.min); - -/** - * ( x y -- max(x,y) ) - * - * @param ctx - */ -export const max = op2(Math.max); - -/** - * ( x -- x+1 ) - * - * @param ctx - */ -export const inc = (ctx: StackContext) => ( - $(ctx[0], 1), ctx[0][ctx[0].length - 1]++, ctx -); - -/** - * ( x -- x-1 ) - * - * @param ctx - */ -export const dec = (ctx: StackContext) => ( - $(ctx[0], 1), ctx[0][ctx[0].length - 1]--, ctx -); - -/** - * ( x -- -x ) - * - * @param ctx - */ -export const neg = op1((x) => -x); - -/** - * ( x y -- pow(x,y) ) - * - * @param ctx - */ -export const pow = op2((b, a) => Math.pow(a, b)); - -/** - * ( x -- sqrt(x) ) - * - * @param ctx - */ -export const sqrt = op1(Math.sqrt); - -/** - * ( x -- exp(x) ) - * - * @param ctx - */ -export const exp = op1(Math.exp); - -/** - * ( x -- log(x) ) - * - * @param ctx - */ -export const log = op1(Math.log); - -/** - * ( x -- sin(x) ) - * - * @param ctx - */ -export const sin = op1(Math.sin); - -/** - * ( x -- cos(x) ) - * - * @param ctx - */ -export const cos = op1(Math.cos); - -/** - * ( x -- tan(x) ) - * - * @param ctx - */ -export const tan = op1(Math.tan); - -/** - * ( x -- tanh(x) ) - * - * @param ctx - */ -export const tanh = op1(Math.tanh); - -/** - * ( x -- floor(x) ) - * - * @param ctx - */ -export const floor = op1(Math.floor); - -/** - * ( x -- ceil(x) ) - * - * @param ctx - */ -export const ceil = op1(Math.ceil); - -/** - * ( x y -- sqrt(x*x+y*y) ) - * - * @param ctx - */ -export const hypot = op2(Math.hypot); - -/** - * ( x y -- atan2(y,x) ) - * - * @param ctx - */ -export const atan2 = op2(Math.atan2); - -/** - * ( -- Math.random() ) - * - * @param ctx - */ -export const rand = (ctx: StackContext) => (ctx[0].push(Math.random()), ctx); - -/** - * ( x -- bool ) - * - * @param ctx - */ -export const even = op1((x) => !(x & 1)); - -/** - * ( x -- bool ) - * - * @param ctx - */ -export const odd = op1((x) => !!(x & 1)); - -//////////////////// Binary ops //////////////////// - -/** - * ( x y -- x&y ) - * - * @param ctx - */ -export const bitand = op2((b, a) => a & b); - -/** - * ( x y -- x|y ) - * - * @param ctx - */ -export const bitor = op2((b, a) => a | b); - -/** - * ( x y -- x^y ) - * - * @param ctx - */ -export const bitxor = op2((b, a) => a ^ b); - -/** - * ( x -- ~x ) - * - * @param ctx - */ -export const bitnot = op1((x) => ~x); - -/** - * ( x y -- x< a << b); - -/** - * ( x y -- x>>y ) - * - * @param ctx - */ -export const lsr = op2((b, a) => a >> b); - -/** - * ( x y -- x>>>y ) - * - * @param ctx - */ -export const lsru = op2((b, a) => a >>> b); - -//////////////////// Logic ops //////////////////// - -/** - * ( x y -- x===y ) - * - * @param ctx - */ -export const eq = op2((b, a) => a === b); - -/** - * ( x y -- equiv(x,y) ) - * - * @param ctx - */ -export const equiv = op2(_equiv); - -/** - * ( x y -- x!==y ) - * - * @param ctx - */ -export const neq = op2((b, a) => a !== b); - -/** - * ( x y -- x&&y ) - * - * @param ctx - */ -export const and = op2((b, a) => !!a && !!b); - -/** - * ( x y -- x||y ) - * - * @param ctx - */ -export const or = op2((b, a) => !!a || !!b); - -/** - * ( x -- !x ) - * - * @param ctx - */ -export const not = op1((x) => !x); - -/** - * ( x y -- x a < b); - -/** - * ( x y -- x>y ) - * - * @param ctx - */ -export const gt = op2((b, a) => a > b); - -/** - * ( x y -- x<=y ) - * - * @param ctx - */ -export const lteq = op2((b, a) => a <= b); - -/** - * ( x y -- x>=y ) - * - * @param ctx - */ -export const gteq = op2((b, a) => a >= b); - -/** - * ( x -- x===0 ) - * - * @param ctx - */ -export const iszero = op1((x) => x === 0); - -/** - * ( x -- x>0 ) - * - * @param ctx - */ -export const ispos = op1((x) => x > 0); - -/** - * ( x -- x<0 ) - * - * @param ctx - */ -export const isneg = op1((x) => x < 0); - -/** - * ( x -- x==null ) - * - * @param ctx - */ -export const isnull = op1((x) => x == null); - -//////////////////// Dataflow combinators //////////////////// - -// these combinators have been ported from Factor: -// http://docs.factorcode.org:8080/content/article-dataflow-combinators.html - -/** - * Removes `x` from d-stack, calls `q` and restores `x` to the top of - * the d-stack after quotation is finished. - * - * ( x q -- x ) - * - * Same behavior as: `[swap, movdr, exec, movrd]`, only the current - * implementation doesn't use r-stack and stashes `x` off stack. - * - * @param ctx - */ -export const dip = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 2); - const q = stack.pop(); - const x = stack.pop(); - ctx = $stackFn(q)(ctx); - ctx[0].push(x); - return ctx; -}; - -/** - * Removes `x y` from d-stack, calls `q` and restores removed vals - * to the top of the d-stack after quotation is finished. - * - * ( x y q -- x y ) - */ -export const dip2 = word([swap, [dip], dip]); - -/** - * Removes `x y z` from d-stack, calls `q` and restores removed - * vals to the top of the d-stack after quotation is finished. - * - * ( x y z q -- x y z ) - */ -export const dip3 = word([swap, [dip2], dip]); - -/** - * Removes `x y z w` from d-stack, calls `q` and restores removed - * vals to the top of the d-stack after quotation is finished. - * - * ( x y z w q -- x y z w ) - */ -export const dip4 = word([swap, [dip3], dip]); - -/** - * Calls a quotation with a value on the d-stack, restoring the value - * after quotation finished. - * - * ( x q -- .. x ) - */ -export const keep = word([over, [exec], dip]); - -/** - * Call a quotation with two values on the stack, restoring the values - * after quotation finished. - * - * ( x y q -- .. x y ) - */ -export const keep2 = word([[dup2], dip, dip2]); - -/** - * Call a quotation with three values on the stack, restoring the values - * after quotation finished. - * - * ( x y z q -- .. x y z ) - */ -export const keep3 = word([[dup3], dip, dip3]); - -/** - * First applies `p` to the value `x`, then applies `q` to the same - * value. - * - * ( x p q -- px qx ) - */ -export const bi = word([[keep], dip, exec]); - -/** - * First applies `p` to the two input values `x y`, then applies `q` to - * the same values. - * - * ( x y p q -- pxy qxy ) - */ -export const bi2 = word([[keep2], dip, exec]); - -/** - * First applies `p` to the three input values `x y z`, then applies `q` - * to the same values. - * - * ( x y z p q -- pxyz qxyz ) - */ -export const bi3 = word([[keep3], dip, exec]); - -/** - * Applies `p` to `x`, then `q` to `x`, and finally `r` to `x` - * - * ( x p q r -- px qx rx ) - */ -export const tri = word([[[keep], dip, keep], dip, exec]); - -/** - * Applies `p` to the two input values `x y`, then same with `q`, and - * finally with `r`. - * - * ( x y p q r -- pxy qxy rxy ) - */ -export const tri2 = word([[[keep2], dip, keep2], dip, exec]); - -/** - * Applies `p` to the three input values `x y z`, then same with `q`, - * and finally with `r`. - * - * ( x y z p q r -- pxyz qxyz rxyz ) - */ -export const tri3 = word([[[keep3], dip, keep3], dip, exec]); - -/** - * Applies `p` to `x`, then applies `q` to `y`. - * - * ( x y p q -- px qy ) - */ -export const bis = word([[dip], dip, exec]); - -/** - * Applies `p` to `a b`, then applies `q` to `c d`. - * - * ( a b c d p q -- pab qcd ) - */ -export const bis2 = word([[dip2], dip, exec]); - -/** - * Applies `p` to `x`, then `q` to `y`, and finally `r` to `z`. - * - * ( x y z p q r -- ) - */ -export const tris = word([[[dip2], dip, dip], dip, exec]); - -/** - * Applies `p` to `u v`, then `q` to `w x`, and finally `r` to `y z`. - * - * ( u v w x y z p q r -- puv qwx ryz ) - */ -export const tris2 = word([[dip4], dip2, bis2]); - -/** - * Applies the quotation `q` to `x`, then to `y`. - * - * ( x y q -- qx qy ) - */ -export const bia = word([dup, bis]); - -/** - * Applies the quotation `q` to `x y`, then to `z w`. - * - * ( x y z w q -- qxy qzw ) - */ -export const bia2 = word([dup, bis2]); - -/** - * Applies the `q` to `x`, then to `y`, and finally to `z`. - * - * ( x y z q -- qx qy qz ) - */ -export const tria = word([dup, dup, tris]); - -/** - * Applies the quotation to `u v`, then to `w x`, and then to `y z`. - * - * ( u v w x y z q -- quv qwx qyz ) - */ -export const tria2 = word([dup, dup, tris2]); - -/** - * Applies `q` individually to both input vals `x y` and combines - * results with `and`. The final result will be true if both interim - * results were truthy. - * - * ( x y q -- qx && qy ) - */ -export const both = word([bia, and]); - -/** - * Applies `q` individually to both input vals `x y` and combines results with `or`. - * The final result will be true if at least one of the interim results - * was truthy. - * - * ( x y q -- qx || qy ) - */ -export const either = word([bia, or]); - -//////////////////// Conditionals //////////////////// - -/** - * Higher order word. Takes two stack programs: truthy and falsey - * branches, respectively. When executed, pops TOS and runs only one of - * the branches depending if TOS was truthy or not. - * - * Note: Unlike JS `if() {...} else {...}` constructs, the actual - * conditional is NOT part of this word. - * - * ( bool -- ? ) - * - * @param _then - * @param _else - */ -export const cond = (_then: StackProc, _else: StackProc = nop) => ( - ctx: StackContext -) => ($(ctx[0], 1), $stackFn(ctx[0].pop() ? _then : _else)(ctx)); - -/** - * Non-HOF version of `cond`, expects `test` result and both branches on - * d-stack. Executes `thenq` word/quotation if `test` is truthy, else - * runs `elseq`. - * - * ( test thenq elseq -- ? ) - * - * @param ctx - */ -export const condq = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 3); - const _else = stack.pop(); - const _then = stack.pop(); - return $stackFn(stack.pop() ? _then : _else)(ctx); -}; - -/** - * Higher order word. Takes an object of stack programs with keys in the - * object being used to check for equality with TOS. If a match is - * found, executes corresponding stack program. If a `default` key is - * specified and no other cases matched, run `default` program. In all - * other cases throws an error. - * - * Important: The default case has the original TOS re-added to the - * d-stack before execution. - * - * @param cases - */ -export const cases = (cases: IObjectOf) => (ctx: StackContext) => { - $(ctx[0], 1); - const stack = ctx[0]; - const tos = stack.pop(); - const cas = cases[tos]; - if (cas !== undefined) { - return $stackFn(cas)(ctx); - } - if (cases.default) { - stack.push(tos); - return $stackFn(cases.default)(ctx); - } - illegalState(`no matching case for: ${tos}`); - return ctx; -}; - -export const casesq = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 2); - return cases(stack.pop())(ctx); -}; - -//////////////////// Loop constructs //////////////////// - -/** - * Higher order word. Takes a `test` and `body` stack program. Applies - * test to copy of TOS and executes body. Repeats while test is truthy. - * - * ( -- ? ) - * - * ``` - * run([loop([dup, ispos], [dup, print, dec])], [[3]]) - * // 3 - * // 2 - * // 1 - * // [ true, [ 0 ], undefined ] - * ``` - * @param test - * @param body - */ -export const loop = (test: StackProc, body: StackProc) => { - const _test = $stackFn(test); - const _body = $stackFn(body); - return (ctx: StackContext) => { - while (true) { - ctx = _test(ctx); - $(ctx[0], 1); - if (!ctx[0].pop()) { - return ctx; - } - ctx = _body(ctx); - } - }; -}; - -/** - * Non-HOF version of `loop`. Expects test result and body quotation / - * word on d-stack. - * - * ( testq bodyq -- ? ) - * - * @param ctx - */ -export const loopq = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 2); - const body = stack.pop(); - return loop(stack.pop(), body)(ctx); -}; - -/** - * Executes given `body` word/quotation `n` times. In each iteration - * pushes current counter on d-stack prior to executing body. - * - * ``` - * pf.run([3, ["i=", pf.swap, pf.add, pf.print], pf.dotimes]) - * // i=0 - * // i=1 - * // i=2 - * ``` - * - * With empty body acts as finite range generator 0 .. n: - * - * ``` - * // range gen - * pf.run([3, [], pf.dotimes]) - * [ [ 0, 1, 2 ], [], {} ] - * - * // range gen (collect results as array) - * pf.runU([3, pf.cpdr, [], pf.dotimes, pf.movrd, pf.collect]) - * // [ 0, 1, 2 ] - * ``` - * - * ( n body -- ? ) - * - * @param body - */ -export const dotimes = (ctx: StackContext) => { - let stack = ctx[0]; - $(stack, 2); - const w = $stackFn(stack.pop()); - for (let i = 0, n = stack.pop(); i < n; i++) { - ctx[0].push(i); - ctx = w(ctx); - } - return ctx; -}; - -//////////////////// Array / list ops //////////////////// - -/** - * Pushes a new empty array on the d-stack. While it's easily possible to - * use `[]` as part of a stack program, the `list` word is intended to - * be used as part of re-usuable `word()` definitions to ensure a new - * array is being created for every single invocation of the word (else - * only a single instance is created due to the mutable nature of JS - * arrays). - * - * Compare: - * - * ``` - * // using array literal within word definition - * foo = pf.word([ [], 1, pf.pushl ]) - * pf.runU(foo) - * // [ 1 ] - * pf.runU(foo) - * // [ 1, 1 ] // wrong! - * - * // using `list` instead - * bar = pf.word([ pf.list, 1, pf.pushl ]) - * pf.runU(bar) - * // [ 1 ] - * pf.runU(bar) - * // [ 1 ] // correct! - * ``` - * - * ( -- [] ) - * - * @param ctx - */ -export const list = (ctx: StackContext) => (ctx[0].push([]), ctx); - -/** - * Pushes new empty JS object on d-stack. - * Same reasoning as for `list`. - * - * ( -- {} ) - * - * @param ctx - */ -export const obj = (ctx: StackContext) => (ctx[0].push({}), ctx); - -/** - * Pushes `val` on the LHS of array. - * - * ( val arr -- arr ) - * - * @param ctx - */ -export const pushl = (ctx: StackContext) => { - $(ctx[0], 2); - const stack = ctx[0]; - const a = stack.pop(); - a.unshift(stack.pop()); - stack.push(a); - return ctx; -}; - -/** - * Pushes `val` on the RHS of array. - * - * ( arr val -- arr ) - * - * @param ctx - */ -export const pushr = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 2; - $n(n, 0); - stack[n].push(stack[n + 1]); - stack.length--; - return ctx; -}; - -/** - * Removes RHS from array as new TOS on d-stack. - * Throws error is `arr` is empty. - * - * ( arr -- arr arr[-1] ) - * - * @param ctx - */ -export const popr = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 1; - $n(n, 0); - const a = stack[n]; - !a.length && illegalState("can't pop empty array"); - stack.push(a.pop()); - return ctx; -}; - -export const pull = word([popr, swap]); -export const pull2 = word([pull, pull]); -export const pull3 = word([pull2, pull]); -export const pull4 = word([pull2, pull2]); - -export const vadd = op2v((b, a) => a + b); -export const vsub = op2v((b, a) => a - b); -export const vmul = op2v((b, a) => a * b); -export const vdiv = op2v((b, a) => a / b); - -/** - * Splits vector / array at given index `x`. - * - * ( arr x -- [...] [...] ) - * - * @param ctx - */ -export const split = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 2; - $n(n, 0); - const a = stack[n]; - const b = stack[n + 1]; - stack[n + 1] = a.splice(b, a.length - b); - return ctx; -}; - -/** - * Concatenates two arrays on d-stack: - * - * ( arr1 arr2 -- arr ) - * - * @param ctx - */ -export const cat = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 2; - $n(n, 0); - stack[n] = stack[n].concat(stack.pop()); - return ctx; -}; - -/** - * Generic array transformer. - * - * ( arr q -- ? ) - * - * Pops both args from d-stack, then executes quotation for each array - * item (each pushed on d-stack prior to calling quotation). Can produce - * any number of results and therefore also be used as filter, mapcat, - * reduce... - * - * ``` - * // each item times 10 - * run([[1, 2, 3, 4], [10, mul], mapl]) - * // [ [ 10, 20, 30, 40 ], [], {} ] - * ``` - * - * Use for filtering: - * - * ``` - * // drop even numbers, duplicate odd ones - * run([[1, 2, 3, 4], [dup, even, cond(drop, dup)], mapl]) - * // [ [ 1, 1, 3, 3 ], [], {} ] - * ``` - * - * Reduction: - * - * ``` - * // the `0` is the initial reduction result - * runU([0, [1, 2, 3, 4], [add], mapl]) - * // 10 - * ``` - * - * **Important**: `mapl` does not produce a result array. However, - * there're several options to collect results as array, e.g. - * - * Use `mapll()` to transform: - * - * ``` - * runU([[1, 2, 3, 4], [10, mul], mapll]) - * // [ 10, 20, 30, 40] - * ``` - * - * Collecting results as array is a form of reduction, so we can use - * `list` to produce an initial new array and `pushr` to push each new - * interim value into the result: - * - * ``` - * runU([list, [1, 2, 3, 4], [10, mul, pushr], mapl]) - * // [ 10, 20, 30, 40 ] - * ``` - * - * If the array size is known & not changed by transformation: - * - * ``` - * runU([[1, 2, 3, 4], [10, mul], mapl, 4, collect]) - * // [ 10, 20, 30, 40 ] - * ``` - * - * @param ctx - */ -export const mapl = (ctx: StackContext) => { - $(ctx[0], 2); - const stack = ctx[0]; - const w = $stackFn(stack.pop()); - const list = stack.pop(); - const n = list.length; - for (let i = 0; i < n; i++) { - ctx[0].push(list[i]); - ctx = w(ctx); - } - return ctx; -}; - -/** - * Similar to `mapl()`, but produces new array of transformed values. - * - * ( arr q -- arr ) - * - * ``` - * runU([[1, 2, 3, 4], [10, mul], mapll]) - * // [ 10, 20, 30, 40] - * ``` - * - * Filter / mapcat: - * - * ``` - * // drop even numbers, duplicate odd ones - * run([[1, 2, 3, 4], [dup, even, cond(drop, dup)], mapll]) - * // [ [ [ 1, 1, 3, 3 ] ], [], {} ] - * ``` - * - * @param ctx - */ -export const mapll = (ctx: StackContext) => { - $(ctx[0], 2); - let stack = ctx[0]; - const w = $stackFn(stack.pop()); - const list = stack.pop(); - const n = list.length; - let r = 0; - for (let i = 0; i < n; i++) { - let m = stack.length; - stack.push(list[i]); - ctx = w(ctx); - stack = ctx[0]; - r += stack.length - m; - } - stack.push(stack.splice(stack.length - r, r)); - return ctx; -}; - -/** - * Convenience wrapper for `mapl` to provide an alternative stack layout - * for reduction purposes: - * - * ( arr q init -- reduction ) - */ -export const foldl = word([invrot, mapl]); - -/** - * Pops TOS (a number) and then forms a tuple of the top `n` remaining - * values and pushes it as new TOS. The original collected stack values - * are removed from d-stack. - * - * ( ... n --- ... [...] ) - * - * @param ctx - */ -export const collect = (ctx: StackContext) => { - const stack = ctx[0]; - let n = stack.length - 1, - m; - $n(n, 0); - $n((n -= m = stack.pop()), 0); - stack.push(stack.splice(n, m)); - return ctx; -}; - -/** - * Higher order helper word to `collect()` tuples of pre-defined size - * `n`. The size can be given as number or a stack function producing a - * number. - * - * ( ... -- [...]) - * - * @param n - */ -export const tuple = (n: number | StackFn) => word([n, collect]); - -export const vec2 = tuple(2); -export const vec3 = tuple(3); -export const vec4 = tuple(4); - -/** - * Higher order helper word to convert a TOS tuple/array into a string - * using `Array.join()` with given `sep`arator. - * - * @param sep - */ -export const join = (sep = "") => op1((x) => x.join(sep)); - -/** - * Pushes length of TOS on d-stack. - * - * ( x -- x.length ) - * - * @param ctx - */ -export const length = op1((x) => x.length); - -/** - * Replaces TOS with its shallow copy. MUST be an array or plain object. - * - * ( x -- copy ) - */ -export const copy = op1((x) => - isArray(x) - ? [...x] - : isPlainObject(x) - ? { ...x } - : illegalArgs(`can't copy type ${typeof x}`) -); - -/** - * Reads key/index from object/array. - * - * ( obj k -- obj[k] ) - * - * @param ctx - */ -export const at = op2((b, a) => a[b]); - -/** - * Writes `val` at key/index in object/array. - * - * ( val obj k -- obj ) - * - * @param ctx - */ -export const setat = (ctx: StackContext) => { - const stack = ctx[0]; - const n = stack.length - 3; - $n(n, 0); - stack[n + 1][stack[n + 2]] = stack[n]; - stack[n] = stack[n + 1]; - stack.length -= 2; - return ctx; -}; - -//////////////////// Objects //////////////////// - -/** - * Takes an array of keys and target object, then pops & binds deeper - * stack values to respective keys in object. Pushes result object back - * on stack at the end. Throws error if there're less stack values than - * keys in given array. - * - * ``` - * runU([1,2,3, ["a","b","c"], {}, bindkeys]) - * // { c: 3, b: 2, a: 1 } - * ``` - * - * (v1 v2 .. [k1 k2 ..] obj -- obj ) - * - * @param ctx - */ -export const bindkeys = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 2); - const obj = stack.pop(); - const keys = stack.pop(); - $(stack, keys.length); - for (let i = keys.length - 1; i >= 0; i--) { - obj[keys[i]] = stack.pop(); - } - stack.push(obj); - return ctx; -}; - -//////////////////// Environment //////////////////// - -/** - * Pushes current env onto d-stack. - * - * ( -- env ) - * - * @param ctx - * @param env - */ -export const pushenv = (ctx: StackContext) => (ctx[0].push(ctx[2]), ctx); - -/** - * Loads value for `key` from current env and pushes it on d-stack. - * Throws error if var doesn't exist. - * - * ( key -- env[key] ) - * - * @param ctx - * @param env - */ -export const load = (ctx: StackContext) => { - const stack = ctx[0]; - $(stack, 1); - const id = stack.pop(); - !ctx[2].hasOwnProperty(id) && illegalArgs(`unknown var: ${id}`); - stack.push(ctx[2][id]); - return ctx; -}; - -/** - * Stores `val` under `key` in env. - * - * ( val key -- ) - * - * @param ctx - * @param env - */ -export const store = (ctx: StackContext) => ( - $(ctx[0], 2), (ctx[2][ctx[0].pop()] = ctx[0].pop()), ctx -); - -/** - * Higher order word. Similar to `load`, but always uses given - * preconfigured `key` instead of reading it from d-stack at runtime - * (also slightly faster). Throws error if var doesn't exist. - * - * ( -- env[key] ) - * @param ctx - * @param env - */ -export const loadkey = (key: PropertyKey) => (ctx: StackContext) => { - !ctx[2].hasOwnProperty(key) && - illegalArgs(`unknown var: ${key.toString()}`); - ctx[0].push(ctx[2][key]); - return ctx; -}; - -/** - * Higher order word. Similar to `store`, but always uses given - * preconfigure `key` instead of reading it from d-stack at runtime - * (also slightly faster). - * - * ( val -- ) - * - * @param ctx - * @param env - */ -export const storekey = (key: PropertyKey) => (ctx: StackContext) => ( - $(ctx[0], 1), (ctx[2][key] = ctx[0].pop()), ctx -); - -//////////////////// I/O //////////////////// - -/** - * Prints TOS to console - * - * ( x -- ) - * - * @param ctx - */ -export const print = (ctx: StackContext) => ( - $(ctx[0], 1), console.log(ctx[0].pop()), ctx -); - -export const printds = (ctx: StackContext) => (console.log(ctx[0]), ctx); - -export const printrs = (ctx: StackContext) => (console.log(ctx[1]), ctx); - export * from "./api"; - -export { $ as ensureStack, $n as ensureStackN }; +export * from "./array"; +export * from "./binary"; +export * from "./cond"; +export * from "./context"; +export * from "./dataflow"; +export * from "./env"; +export * from "./io"; +export * from "./logic"; +export * from "./loop"; +export * from "./math"; +export * from "./run"; +export * from "./safe"; +export * from "./stack"; +export * from "./word"; + +export { $ as ensureStack, $n as ensureStackN } from "./safe"; +export { op1 as maptos, op2 as map2, op2v } from "./ops"; diff --git a/packages/pointfree/src/io.ts b/packages/pointfree/src/io.ts new file mode 100644 index 0000000000..f43486ab1e --- /dev/null +++ b/packages/pointfree/src/io.ts @@ -0,0 +1,19 @@ +import { StackContext } from "./api"; +import { $ } from "./safe"; + +//////////////////// I/O //////////////////// + +/** + * Prints TOS to console + * + * ( x -- ) + * + * @param ctx + */ +export const print = (ctx: StackContext) => ( + $(ctx[0], 1), console.log(ctx[0].pop()), ctx +); + +export const printds = (ctx: StackContext) => (console.log(ctx[0]), ctx); + +export const printrs = (ctx: StackContext) => (console.log(ctx[1]), ctx); diff --git a/packages/pointfree/src/logic.ts b/packages/pointfree/src/logic.ts new file mode 100644 index 0000000000..0aaf4cd403 --- /dev/null +++ b/packages/pointfree/src/logic.ts @@ -0,0 +1,102 @@ +import { equiv as _equiv } from "@thi.ng/equiv"; +import { op1, op2 } from "./ops"; + +//////////////////// Logic ops //////////////////// + +/** + * ( x y -- x===y ) + * + * @param ctx + */ +export const eq = op2((b, a) => a === b); + +/** + * ( x y -- equiv(x,y) ) + * + * @param ctx + */ +export const equiv = op2(_equiv); + +/** + * ( x y -- x!==y ) + * + * @param ctx + */ +export const neq = op2((b, a) => a !== b); + +/** + * ( x y -- x&&y ) + * + * @param ctx + */ +export const and = op2((b, a) => !!a && !!b); + +/** + * ( x y -- x||y ) + * + * @param ctx + */ +export const or = op2((b, a) => !!a || !!b); + +/** + * ( x -- !x ) + * + * @param ctx + */ +export const not = op1((x) => !x); + +/** + * ( x y -- x a < b); + +/** + * ( x y -- x>y ) + * + * @param ctx + */ +export const gt = op2((b, a) => a > b); + +/** + * ( x y -- x<=y ) + * + * @param ctx + */ +export const lteq = op2((b, a) => a <= b); + +/** + * ( x y -- x>=y ) + * + * @param ctx + */ +export const gteq = op2((b, a) => a >= b); + +/** + * ( x -- x===0 ) + * + * @param ctx + */ +export const iszero = op1((x) => x === 0); + +/** + * ( x -- x>0 ) + * + * @param ctx + */ +export const ispos = op1((x) => x > 0); + +/** + * ( x -- x<0 ) + * + * @param ctx + */ +export const isneg = op1((x) => x < 0); + +/** + * ( x -- x==null ) + * + * @param ctx + */ +export const isnull = op1((x) => x == null); diff --git a/packages/pointfree/src/loop.ts b/packages/pointfree/src/loop.ts new file mode 100644 index 0000000000..f650a4cf5a --- /dev/null +++ b/packages/pointfree/src/loop.ts @@ -0,0 +1,89 @@ +import { StackContext, StackProc } from "./api"; +import { $ } from "./safe"; +import { $stackFn } from "./word"; + +//////////////////// Loop constructs //////////////////// + +/** + * Higher order word. Takes a `test` and `body` stack program. Applies + * test to copy of TOS and executes body. Repeats while test is truthy. + * + * ( -- ? ) + * + * ``` + * run([loop([dup, ispos], [dup, print, dec])], [[3]]) + * // 3 + * // 2 + * // 1 + * // [ true, [ 0 ], undefined ] + * ``` + * @param test + * @param body + */ +export const loop = (test: StackProc, body: StackProc) => { + const _test = $stackFn(test); + const _body = $stackFn(body); + return (ctx: StackContext) => { + while (true) { + ctx = _test(ctx); + $(ctx[0], 1); + if (!ctx[0].pop()) { + return ctx; + } + ctx = _body(ctx); + } + }; +}; + +/** + * Non-HOF version of `loop`. Expects test result and body quotation / + * word on d-stack. + * + * ( testq bodyq -- ? ) + * + * @param ctx + */ +export const loopq = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 2); + const body = stack.pop(); + return loop(stack.pop(), body)(ctx); +}; + +/** + * Executes given `body` word/quotation `n` times. In each iteration + * pushes current counter on d-stack prior to executing body. + * + * ``` + * pf.run([3, ["i=", pf.swap, pf.add, pf.print], pf.dotimes]) + * // i=0 + * // i=1 + * // i=2 + * ``` + * + * With empty body acts as finite range generator 0 .. n: + * + * ``` + * // range gen + * pf.run([3, [], pf.dotimes]) + * [ [ 0, 1, 2 ], [], {} ] + * + * // range gen (collect results as array) + * pf.runU([3, pf.cpdr, [], pf.dotimes, pf.movrd, pf.collect]) + * // [ 0, 1, 2 ] + * ``` + * + * ( n body -- ? ) + * + * @param body + */ +export const dotimes = (ctx: StackContext) => { + let stack = ctx[0]; + $(stack, 2); + const w = $stackFn(stack.pop()); + for (let i = 0, n = stack.pop(); i < n; i++) { + ctx[0].push(i); + ctx = w(ctx); + } + return ctx; +}; diff --git a/packages/pointfree/src/math.ts b/packages/pointfree/src/math.ts new file mode 100644 index 0000000000..28ae436b75 --- /dev/null +++ b/packages/pointfree/src/math.ts @@ -0,0 +1,174 @@ +import { StackContext } from "./api"; +import { op1, op2 } from "./ops"; +import { swap } from "./stack"; +import { word } from "./word"; + +//////////////////// Math ops //////////////////// + +/** + * ( x y -- x+y ) + * + * @param ctx + */ +export const add = op2((b, a) => a + b); + +/** + * ( x y -- x*y ) + * + * @param ctx + */ +export const mul = op2((b, a) => a * b); + +/** + * ( x y -- x-y ) + * + * @param ctx + */ +export const sub = op2((b, a) => a - b); + +/** + * ( x y -- x/y ) + * + * @param ctx + */ +export const div = op2((b, a) => a / b); + +/** + * ( x -- 1/x ) + * + * @param ctx + */ +export const oneover = word([1, swap, div]); + +/** + * ( x y -- x%y ) + * + * @param ctx + */ +export const mod = op2((b, a) => a % b); + +/** + * ( x y -- min(x,y) ) + * + * @param ctx + */ +export const min = op2(Math.min); + +/** + * ( x y -- max(x,y) ) + * + * @param ctx + */ +export const max = op2(Math.max); + +/** + * ( x -- -x ) + * + * @param ctx + */ +export const neg = op1((x) => -x); + +/** + * ( x y -- pow(x,y) ) + * + * @param ctx + */ +export const pow = op2((b, a) => Math.pow(a, b)); + +/** + * ( x -- sqrt(x) ) + * + * @param ctx + */ +export const sqrt = op1(Math.sqrt); + +/** + * ( x -- exp(x) ) + * + * @param ctx + */ +export const exp = op1(Math.exp); + +/** + * ( x -- log(x) ) + * + * @param ctx + */ +export const log = op1(Math.log); + +/** + * ( x -- sin(x) ) + * + * @param ctx + */ +export const sin = op1(Math.sin); + +/** + * ( x -- cos(x) ) + * + * @param ctx + */ +export const cos = op1(Math.cos); + +/** + * ( x -- tan(x) ) + * + * @param ctx + */ +export const tan = op1(Math.tan); + +/** + * ( x -- tanh(x) ) + * + * @param ctx + */ +export const tanh = op1(Math.tanh); + +/** + * ( x -- floor(x) ) + * + * @param ctx + */ +export const floor = op1(Math.floor); + +/** + * ( x -- ceil(x) ) + * + * @param ctx + */ +export const ceil = op1(Math.ceil); + +/** + * ( x y -- sqrt(x*x+y*y) ) + * + * @param ctx + */ +export const hypot = op2(Math.hypot); + +/** + * ( x y -- atan2(y,x) ) + * + * @param ctx + */ +export const atan2 = op2(Math.atan2); + +/** + * ( -- Math.random() ) + * + * @param ctx + */ +export const rand = (ctx: StackContext) => (ctx[0].push(Math.random()), ctx); + +/** + * ( x -- bool ) + * + * @param ctx + */ +export const even = op1((x) => !(x & 1)); + +/** + * ( x -- bool ) + * + * @param ctx + */ +export const odd = op1((x) => !!(x & 1)); diff --git a/packages/pointfree/src/ops.ts b/packages/pointfree/src/ops.ts new file mode 100644 index 0000000000..38a2d901e5 --- /dev/null +++ b/packages/pointfree/src/ops.ts @@ -0,0 +1,96 @@ +import { Fn, Fn2 } from "@thi.ng/api"; +import { isArray } from "@thi.ng/checks"; +import { illegalArgs } from "@thi.ng/errors"; +import { StackContext } from "./api"; +import { $, $n } from "./safe"; + +//////////////////// Operator generators //////////////////// + +/** + * Higher order word. Replaces TOS of d-stack with result of given op. + * + * ( x -- y ) + * + * @param op + */ +export const op1 = (op: Fn) => { + return (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 1; + $n(n, 0); + stack[n] = op(stack[n]); + return ctx; + }; +}; + +/** + * Higher order word. Takes 2 values from d-stack and writes back result + * from given op. The arg order is (TOS, TOS-1) + * + * ( a b -- c ) + * + * @param op + */ +export const op2 = (op: Fn2) => (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 2; + $n(n, 0); + stack[n] = op(stack.pop(), stack[n]); + return ctx; +}; + +/** + * Similar to `op2`, but for array operators. Either `a` or `b` can be a + * non-array value, but not both. Creates new array of result values. + * The result will have the same length as the shortest arg (if `a` and + * `b` have different lengths). + * + * - ( a b -- a ), if `a` is an array + * - ( a b -- b ), if `a` is not an array + * + * @param f + */ +export const op2v = (f: Fn2) => ( + ctx: StackContext +): StackContext => { + $(ctx[0], 2); + const stack = ctx[0]; + const b = stack.pop(); + const n = stack.length - 1; + const a = stack[n]; + const isa = isArray(a); + const isb = isArray(b); + stack[n] = + isa && isb + ? op2vAB(f, a, b) + : isb && !isa + ? op2vB(f, a, b) + : isa && !isb + ? op2vA(f, a, b) + : illegalArgs("at least one arg must be an array"); + return ctx; +}; + +const op2vAB = (f: Fn2, a: any, b: any) => { + const res = new Array(Math.min(a.length, b.length)); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = f(b[i], a[i]); + } + return res; +}; + +const op2vA = (f: Fn2, a: any, b: any) => { + const res = new Array(a.length); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = f(b, a[i]); + } + return res; +}; + +const op2vB = (f: Fn2, a: any, b: any) => { + const res = new Array(b.length); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = f(b[i], a); + } + return res; +}; diff --git a/packages/pointfree/src/run.ts b/packages/pointfree/src/run.ts new file mode 100644 index 0000000000..fbad7f3ba3 --- /dev/null +++ b/packages/pointfree/src/run.ts @@ -0,0 +1,52 @@ +import { isArray, isFunction } from "@thi.ng/checks"; +import { StackContext, StackProc } from "./api"; +import { unwrap } from "./word"; + +/** + * Executes program / quotation with given stack context (initial D/R + * stacks and optional environment). Returns updated context. + * + * @param prog + * @param ctx + */ +export const run = ( + prog: StackProc, + ctx: StackContext = [[], [], {}] +): StackContext => { + if (isFunction(prog)) { + return prog(ctx); + } + for ( + let p = isArray(prog) ? prog : [prog], n = p.length, i = 0, w; + i < n; + i++ + ) { + if (isFunction((w = p[i]))) { + ctx = w(ctx); + } else { + ctx[0].push(w); + } + } + return ctx; +}; + +/** + * Like `run()`, but returns unwrapped result. Syntax sugar for: + * `unwrap(run(...),n)` + * + * @param prog + * @param ctx + * @param n + */ +export const runU = (prog: StackProc, ctx?: StackContext, n = 1) => + unwrap(run(prog, ctx), n); + +/** + * Like `run()`, but returns result environment. Syntax sugar for: + * `run(...)[2]` + * + * @param prog + * @param ctx + * @param n + */ +export const runE = (prog: StackProc, ctx?: StackContext) => run(prog, ctx)[2]; diff --git a/packages/pointfree/src/safe.ts b/packages/pointfree/src/safe.ts new file mode 100644 index 0000000000..7ede2ab06c --- /dev/null +++ b/packages/pointfree/src/safe.ts @@ -0,0 +1,18 @@ +import { Fn2, NO_OP } from "@thi.ng/api"; +import { illegalState } from "@thi.ng/errors"; +import { Stack } from "./api"; + +// ensure stack size +export let $: Fn2; +export let $n: Fn2; + +export const safeMode = (state: boolean) => { + if (state) { + $n = (m: number, n: number) => m < n && illegalState(`stack underflow`); + $ = (stack: Stack, n: number) => $n(stack.length, n); + } else { + $ = $n = NO_OP; + } +}; + +safeMode(true); diff --git a/packages/pointfree/src/stack.ts b/packages/pointfree/src/stack.ts new file mode 100644 index 0000000000..d7d6a4c23f --- /dev/null +++ b/packages/pointfree/src/stack.ts @@ -0,0 +1,434 @@ +import { Stack, StackContext } from "./api"; +import { $, $n } from "./safe"; + +const __xsp = (id: 0 | 1) => (ctx: StackContext) => ( + ctx[0].push(ctx[id].length), ctx +); + +const __dup = (id: 0 | 1) => __copy(id, id); + +const __dup2 = (id: 0 | 1) => (ctx: StackContext) => { + const stack = ctx[id]; + let n = stack.length - 2; + $n(n, 0); + stack.push(stack[n], stack[n + 1]); + return ctx; +}; + +const __dup3 = (id: 0 | 1) => (ctx: StackContext) => { + const stack = ctx[id]; + let n = stack.length - 3; + $n(n, 0); + stack.push(stack[n], stack[n + 1], stack[n + 2]); + return ctx; +}; + +const __drop = (id: 0 | 1, n = 1) => (ctx: StackContext) => ( + $(ctx[id], 1), (ctx[id].length -= n), ctx +); + +const __swap = (i: number) => (ctx: StackContext) => { + const stack = ctx[i]; + const n = stack.length - 2; + $n(n, 0); + const a = stack[n]; + stack[n] = stack[n + 1]; + stack[n + 1] = a; + return ctx; +}; + +const __swap2 = (i: number) => (ctx: StackContext) => { + const stack = ctx[i]; + let n = stack.length - 1; + $n(n, 3); + let a = stack[n]; + stack[n] = stack[n - 2]; + stack[n - 2] = a; + n--; + a = stack[n]; + stack[n] = stack[n - 2]; + stack[n - 2] = a; + return ctx; +}; + +const __over = (id: 0 | 1) => (ctx: StackContext) => { + const stack = ctx[id]; + const n = stack.length - 2; + $n(n, 0); + stack.push(stack[n]); + return ctx; +}; + +const __move = (src: 0 | 1, dest: 0 | 1) => (ctx: StackContext) => ( + $(ctx[src], 1), ctx[dest].push(ctx[src].pop()), ctx +); + +const __move2 = (a: 0 | 1, b: 0 | 1) => (ctx: StackContext) => { + const src = ctx[a]; + $(src, 2); + const v = src.pop(); + ctx[b].push(src.pop(), v); + return ctx; +}; + +const __copy = (src: 0 | 1, dest: 0 | 1) => (ctx: StackContext) => ( + $(ctx[src], 1), ctx[dest].push(tos(ctx[src])), ctx +); + +const __copy2 = (a: 0 | 1, b: 0 | 1) => (ctx: StackContext) => { + const src = ctx[a]; + const n = src.length - 2; + $n(n, 0); + ctx[b].push(src[n], src[n + 1]); + return ctx; +}; + +const __incdec = (id: 0 | 1, n: number) => (ctx: StackContext) => ( + $(ctx[id], 1), (ctx[id][ctx[id].length - 1] += n), ctx +); + +//////////////////// Stack manipulation words //////////////////// + +/** + * Returns top of stack value (always unsafe, no underflow checking). + * + * @param stack + */ +export const tos = (stack: Stack) => stack[stack.length - 1]; + +/** + * Utility word w/ no stack nor side effect. + */ +export const nop = (ctx: StackContext) => ctx; + +/** + * Pushes current d-stack size on d-stack. + * + * ( -- n ) + * @param ctx + */ +export const dsp = __xsp(0); + +/** + * Uses TOS as index to look up a deeper d-stack value, then places it + * as new TOS. Throws error if stack depth is < `x`. + * + * ( ... x -- ... stack[x] ) + * + * @param ctx + */ +export const pick = (ctx: StackContext) => { + const stack = ctx[0]; + let n = stack.length - 1; + $n(n, 0); + $n((n -= stack.pop() + 1), 0); + stack.push(stack[n]); + return ctx; +}; + +/** + * Removes TOS from d-stack. + * + * ( x -- ) + * + * @param ctx + */ +export const drop = __drop(0); + +/** + * Removes top 2 vals from d-stack. + * + * ( x y -- ) + * + * @param ctx + */ +export const drop2 = __drop(0, 2); + +/** + * If TOS is truthy then drop it: + * + * ( x -- ) + * + * Else, no effect: + * + * ( x -- x ) + */ +export const dropif = (ctx: StackContext) => ( + $(ctx[0], 1), tos(ctx[0]) && ctx[0].length--, ctx +); + +/** + * Higher order word. Pushes given args verbatim on d-stack. + * + * ( -- ...args ) + * + * @param args + */ +export const push = (...args: any[]) => (ctx: StackContext) => ( + ctx[0].push(...args), ctx +); + +/** + * Duplicates TOS on d-stack. + * + * ( x -- x x ) + * + * @param ctx + */ +export const dup = __dup(0); + +/** + * Duplicates top 2 vals on d-stack. + * + * ( x y -- x y x y ) + * + * @param ctx + */ +export const dup2 = __dup2(0); + +/** + * Duplicates top 3 vals on d-stack. + * + * ( x y -- x y x y ) + * + * @param ctx + */ +export const dup3 = __dup3(0); + +/** + * If TOS is truthy then push copy of it on d-stack: + * + * ( x -- x x ) + * + * Else, no effect: + * + * ( x -- x ) + * + * @param ctx + */ +export const dupif = (ctx: StackContext) => { + $(ctx[0], 1); + const x = tos(ctx[0]); + x && ctx[0].push(x); + return ctx; +}; + +/** + * Swaps the two topmost d-stack items. + * + * ( x y -- y x ) + * + * @param ctx + */ +export const swap = __swap(0); + +/** + * Swaps the two topmost d-stack pairs. + * + * ( a b c d -- c d a b ) + * + * @param ctx + */ +export const swap2 = __swap2(0); + +/** + * Removes second topmost item from d-stack. + * + * ( x y -- y ) + * + * @param ctx + */ +export const nip = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 2; + $n(n, 0); + stack[n] = stack.pop(); + return ctx; +}; + +/** + * Inserts copy of TOS @ TOS-2 in d-stack. + * + * ( x y -- y x y ) + * + * @param ctx + */ +export const tuck = (ctx: StackContext) => { + $(ctx[0], 2); + const stack = ctx[0]; + const a = stack.pop(); + stack.push(a, stack.pop(), a); + return ctx; +}; + +/** + * Rotates three topmost d-stack items downwards/to the left. + * + * ( x y z -- y z x ) + * + * @param ctx + */ +export const rot = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 1; + $n(n, 2); + const c = stack[n - 2]; + stack[n - 2] = stack[n - 1]; + stack[n - 1] = stack[n]; + stack[n] = c; + return ctx; +}; + +/** + * Rotates three topmost d-stack items upwards/to the right. + * + * ( x y z -- z x y ) + * + * @param ctx + */ +export const invrot = (ctx: StackContext) => { + const stack = ctx[0]; + const n = stack.length - 1; + $n(n, 2); + const c = stack[n]; + stack[n] = stack[n - 1]; + stack[n - 1] = stack[n - 2]; + stack[n - 2] = c; + return ctx; +}; + +/** + * Pushes copy of TOS-1 as new TOS on d-stack. + * + * ( x y -- x y x ) + * + * @param ctx + */ +export const over = __over(0); + +/** + * ( x -- x+1 ) + * + * @param ctx + */ +export const inc = __incdec(0, 1); + +/** + * ( x -- x-1 ) + * + * @param ctx + */ +export const dec = __incdec(0, -1); + +//////////////////// R-Stack ops //////////////////// + +/** + * Pushes current r-stack size on d-stack. + * + * ( -- n ) + * + * @param ctx + */ +export const rsp = __xsp(1); + +/** + * Duplicates TOS on r-stack. + * + * ( x -- x x ) + * + * @param ctx + */ +export const rdup = __dup(1); + +/** + * Duplicates top 2 vals on r-stack. + * + * ( x y -- x y x y ) + * + * @param ctx + */ +export const rdup2 = __dup2(1); + +/** + * Duplicates top 3 vals on r-stack. + * + * ( x y -- x y x y ) + * + * @param ctx + */ +export const rdup3 = __dup3(1); + +/** + * Removes TOS from r-stack. + * + * ( x -- ) + * + * @param ctx + */ +export const rdrop = __drop(1); + +/** + * Removes top 2 vals from r-stack. + * + * ( x y -- ) + * + * @param ctx + */ +export const rdrop2 = __drop(1, 2); + +export const movdr = __move(0, 1); + +export const movrd = __move(1, 0); + +export const cpdr = __copy(0, 1); + +export const cprd = __copy(1, 0); + +export const movdr2 = __move2(0, 1); + +export const movrd2 = __move2(1, 0); + +export const cpdr2 = __copy2(0, 1); + +export const cprd2 = __copy2(1, 0); + +/** + * Swaps the two topmost r-stack items. + * + * ( x y -- y x ) + * + * @param ctx + */ +export const rswap = __swap(1); + +/** + * Swaps the two topmost d-stack pairs. + * + * ( a b c d -- c d a b ) + * + * @param ctx + */ +export const rswap2 = __swap2(1); + +/** + * Pushes copy of TOS-1 as new TOS on r-stack. + * + * ( x y -- x y x ) + * + * @param ctx + */ +export const rover = __over(1); + +/** + * Like `inc`, but applies to r-stack TOS. + * + * @param ctx + */ +export const rinc = __incdec(1, 1); + +/** + * Like `dec`, but applies to r-stack TOS. + * + * @param ctx + */ +export const rdec = __incdec(1, -1); diff --git a/packages/pointfree/src/word.ts b/packages/pointfree/src/word.ts new file mode 100644 index 0000000000..3c3334fc1a --- /dev/null +++ b/packages/pointfree/src/word.ts @@ -0,0 +1,122 @@ +import { isArray, isFunction } from "@thi.ng/checks"; +import { compL } from "@thi.ng/compose"; +import { + StackContext, + StackEnv, + StackFn, + StackProc, + StackProgram +} from "./api"; +import { $ } from "./safe"; +import { tos } from "./stack"; + +export const $stackFn = (f: StackProc) => (isArray(f) ? word(f) : f); + +const compile = (prog: StackProgram) => + compL.apply(null, ( + prog.map((w) => + !isFunction(w) ? (ctx: StackContext) => (ctx[0].push(w), ctx) : w + ) + )); + +/** + * Takes a result tuple returned by `run()` and unwraps one or more + * items from result stack. If no `n` is given, defaults to single value + * (TOS) and returns it as is. Returns an array for all other `n`. + * + * @param result + * @param n + */ +export const unwrap = ([stack]: StackContext, n = 1) => + n === 1 ? tos(stack) : stack.slice(Math.max(0, stack.length - n)); + +//////////////////// Dynamic words & quotations //////////////////// + +/** + * Higher order word. Takes a StackProgram and returns it as StackFn to + * be used like any word. Unknown stack effect. + * + * If the optional `env` is given, uses a shallow copy of that + * environment (one per invocation) instead of the current one passed by + * `run()` at runtime. If `mergeEnv` is true (default), the user + * provided env will be merged with the current env (also shallow + * copies). This is useful in conjunction with `pushenv()` and `store()` + * or `storekey()` to save results of sub procedures in the main env. + * + * Note: The provided (or merged) env is only active within the + * execution scope of the word. + * + * ( ? -- ? ) + * + * @param prog + * @param env + * @param mergeEnv + */ +export const word = (prog: StackProgram, env?: StackEnv, mergeEnv = true) => { + const w: StackFn = compile(prog); + return env + ? mergeEnv + ? (ctx: StackContext) => ( + w([ctx[0], ctx[1], { ...ctx[2], ...env }]), ctx + ) + : (ctx: StackContext) => (w([ctx[0], ctx[1], { ...env }]), ctx) + : w; +}; + +/** + * Like `word()`, but automatically calls `unwrap()` on result context + * to produced unwrapped value/tuple. + * + * **Importatant:** Words defined with this function CANNOT be used as + * part of a larger stack program, only for standalone use. + * + * @param prog + * @param n + * @param env + * @param mergeEnv + */ +export const wordU = ( + prog: StackProgram, + n = 1, + env?: StackEnv, + mergeEnv = true +) => { + const w: StackFn = compile(prog); + return env + ? mergeEnv + ? (ctx: StackContext) => + unwrap(w([ctx[0], ctx[1], { ...ctx[2], ...env }]), n) + : (ctx: StackContext) => unwrap(w([ctx[0], ctx[1], { ...env }]), n) + : (ctx: StackContext) => unwrap(w(ctx), n); +}; + +/** + * Executes TOS as stack function and places result back on d-stack. TOS + * MUST be a valid word or quotation. + * + * ( x -- x() ) + * + * @param ctx + */ +export const exec = (ctx: StackContext) => ( + $(ctx[0], 1), $stackFn(ctx[0].pop())(ctx) +); + +//////////////////// JS host calls //////////////////// + +/** + * Expects TOS to be a quotation with a vanilla JS function as first + * element. Calls fn with all remaining items in quot as arguments and + * pushes result back on d-stack (even if fn returned `undefined`). + * + * ( [f ...] -- f(...) ) + * + * @param ctx + */ +export const execjs = (ctx: StackContext) => { + const stack = ctx[0]; + $(stack, 1); + const [fn, ...args] = stack.pop(); + stack.push(fn(...args)); + return ctx; +}; diff --git a/packages/poisson/CHANGELOG.md b/packages/poisson/CHANGELOG.md index 5cf7b41811..210baffc19 100644 --- a/packages/poisson/CHANGELOG.md +++ b/packages/poisson/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/poisson@0.2.25...@thi.ng/poisson@0.2.26) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/poisson + + + + + +## [0.2.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/poisson@0.2.24...@thi.ng/poisson@0.2.25) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/poisson + + + + + +## [0.2.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/poisson@0.2.23...@thi.ng/poisson@0.2.24) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/poisson + + + + + +## [0.2.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/poisson@0.2.22...@thi.ng/poisson@0.2.23) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/poisson + + + + + +## [0.2.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/poisson@0.2.21...@thi.ng/poisson@0.2.22) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/poisson + + + + + +## [0.2.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/poisson@0.2.20...@thi.ng/poisson@0.2.21) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/poisson + + + + + ## [0.2.20](https://github.com/thi-ng/umbrella/compare/@thi.ng/poisson@0.2.19...@thi.ng/poisson@0.2.20) (2019-07-31) **Note:** Version bump only for package @thi.ng/poisson diff --git a/packages/poisson/README.md b/packages/poisson/README.md index d7a90aa282..6901007171 100644 --- a/packages/poisson/README.md +++ b/packages/poisson/README.md @@ -48,7 +48,7 @@ yarn add @thi.ng/poisson ## Usage examples -![example output](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/poisson.jpg) +![example output](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/poisson/poisson.jpg) ```ts import { samplePoisson } from "@thi.ng/poisson"; diff --git a/packages/poisson/package.json b/packages/poisson/package.json index 32d448ebf1..4b4c13e19c 100644 --- a/packages/poisson/package.json +++ b/packages/poisson/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/poisson", - "version": "0.2.20", + "version": "0.2.26", "description": "nD Poisson-disc sampling w/ support for spatial density functions and custom PRNGs", "module": "./index.js", "main": "./lib/index.js", @@ -29,14 +29,14 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/checks": "^2.2.2", - "@thi.ng/geom-api": "^0.3.1", - "@thi.ng/random": "^1.1.10", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/checks": "^2.4.1", + "@thi.ng/geom-api": "^0.3.7", + "@thi.ng/random": "^1.1.13", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "2d", diff --git a/packages/porter-duff/CHANGELOG.md b/packages/porter-duff/CHANGELOG.md index c2aa417c33..9e420e8bbf 100644 --- a/packages/porter-duff/CHANGELOG.md +++ b/packages/porter-duff/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/porter-duff@0.1.3...@thi.ng/porter-duff@0.1.4) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/porter-duff + + + + + +## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/porter-duff@0.1.2...@thi.ng/porter-duff@0.1.3) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/porter-duff + + + + + +## [0.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/porter-duff@0.1.1...@thi.ng/porter-duff@0.1.2) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/porter-duff + + + + + +## [0.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/porter-duff@0.1.0...@thi.ng/porter-duff@0.1.1) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/porter-duff + + + + + # 0.1.0 (2019-07-31) diff --git a/packages/porter-duff/README.md b/packages/porter-duff/README.md index 06a062cfb1..15f03963b6 100644 --- a/packages/porter-duff/README.md +++ b/packages/porter-duff/README.md @@ -13,6 +13,7 @@ This project is part of the - [References](#references) - [Installation](#installation) - [Dependencies](#dependencies) +- [Related packages](#related-packages) - [Usage examples](#usage-examples) - [API](#api) - [Operators](#operators) @@ -36,7 +37,7 @@ ints or RGBA float vectors. [@thi.ng/color](https://github.com/thi-ng/umbrella/tree/master/packages/color) package (prior to v1.0.0). -![porter-duff compositing modes](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/porter-duff2.png) +![porter-duff compositing modes](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/porter-duff/porter-duff2.png) ### References @@ -56,12 +57,16 @@ yarn add @thi.ng/porter-duff - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) +## Related packages + +- [@thi.ng/pixel](https://github.com/thi-ng/umbrella/tree/master/packages/pixel) - Multi-format pixel buffers + ## Usage examples Full overview of all operators (shown above): [Live demo](http://demo.thi.ng/umbrella/porter-duff/) | -[Source](https://github.com/thi-ng/umbrella/tree/develop/examples/porter-duff) +[Source](https://github.com/thi-ng/umbrella/tree/master/examples/porter-duff) Basic usage... @@ -72,7 +77,7 @@ import * as pd from "@thi.ng/porter-duff"; pd.SRC_OVER_I(0x80800000, 0xcc0cc00) // automatically premultiply inputs & post-multiply result -pd.porterDuffPInt(pd.SRC_OVER_I, 0x80ff0000, 0xcc00cc00); +pd.porterDuffPInt(pd.SRC_OVER_I, 0x80ff0000, 0xcc00ff00); // the above is same as: pd.postmultiplyInt( @@ -109,7 +114,9 @@ Consult above diagram for expected results. ### Custom operators -New operators (e.g. for blend modes) can be easily defined via `porterDuff` / `porterDuffInt`. Both functions take 2 function arguments to extract blend coefficients from the src & dest colors: +New operators (e.g. for blend modes) can be easily defined via +`porterDuff` / `porterDuffInt`. Both functions take 2 function arguments +to extract blend coefficients from the src & dest colors: ```ts // coefficient functions take the normalized alpha values @@ -117,7 +124,17 @@ New operators (e.g. for blend modes) can be easily defined via `porterDuff` / `p const customOp = porterDuffInt(() => -0.5, () => 1); ``` -![custom operator](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/porter-duff-custom.png) +![custom operator](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/porter-duff/porter-duff-custom.png) + +The following coefficient functions are included by default (and are +used by all standard operators): + +- `ZERO` => 0 +- `ONE` => 1 +- `A` => alpha of src color +- `B` => alpha of dest color +- `ONE_MINUS_A` => 1 - alpha of src color +- `ONE_MINUS_B` => 1 - alpha of dest color ### Additional operators / modifiers @@ -130,9 +147,9 @@ The following modifiers are also discussed in the original Porter-Duff paper (li ### Pre/post-multiplied colors All Porter-Duff operators expect colors with **pre-multiplied** alpha. -Premultiplication is also recommended for WebGL textures (especially -when using mipmaps). For that purpose the following helpers might be -useful: +Premultiplication is also recommended for transparent WebGL textures +(especially when using mipmaps). For that purpose the following helpers +might be useful: - `premultiply` / `premultiplyInt` - `postmultiply` / `postmultiplyInt` diff --git a/packages/porter-duff/package.json b/packages/porter-duff/package.json index e1d7d54715..5cd15f99b9 100644 --- a/packages/porter-duff/package.json +++ b/packages/porter-duff/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/porter-duff", - "version": "0.1.0", + "version": "0.1.4", "description": "Porter-Duff operators for packed ints & float-array alpha compositing", "module": "./index.js", "main": "./lib/index.js", @@ -29,11 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/math": "^1.5.0" }, "keywords": [ "alpha blending", diff --git a/packages/porter-duff/src/porter-duff.ts b/packages/porter-duff/src/porter-duff.ts index 16ef0de58b..c23503d11c 100644 --- a/packages/porter-duff/src/porter-duff.ts +++ b/packages/porter-duff/src/porter-duff.ts @@ -1,5 +1,5 @@ import { Fn2, Fn3 } from "@thi.ng/api"; -import { clamp } from "@thi.ng/math"; +import { clamp, clamp01 } from "@thi.ng/math"; import { Color, ReadonlyColor } from "./api"; import { postmultiply, @@ -27,8 +27,11 @@ export const ONE_MINUS_B = (_: number, b: number) => 1 - b; * - `src` color (background) * - `dest` color (foreground) * - * Reference: - * https://keithp.com/~keithp/porterduff/p253-porter.pdf + * Unlike the packed int version, here only the alpha channel of the + * result color will be clamped. RGB components can potentially go out + * of [0..1] range (depending on coefficient functions used). + * + * Reference: https://keithp.com/~keithp/porterduff/p253-porter.pdf * * @param fa fn for src coeff * @param fb fn for dest coeff @@ -46,7 +49,7 @@ export const porterDuff = ( src[0] * aa + dest[0] * bb, src[1] * aa + dest[1] * bb, src[2] * aa + dest[2] * bb, - min(1, src[3] * aa + dest[3] * bb) + clamp01(src[3] * aa + dest[3] * bb) ); }; @@ -201,7 +204,7 @@ export const PLUS_F = porterDuff(ONE, ONE); ////////// Packed ARGB / ABGR versions ////////// -export const CLEAR_I: Fn2 = () => 0; +export const CLEAR_I = >ZERO; /** * Porter-Duff operator for packed ints. Always results in `src` color, `dest` ignored. diff --git a/packages/quad-edge/CHANGELOG.md b/packages/quad-edge/CHANGELOG.md index f746aeea3c..2e2f602de1 100644 --- a/packages/quad-edge/CHANGELOG.md +++ b/packages/quad-edge/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/quad-edge@0.2.2...@thi.ng/quad-edge@0.2.3) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/quad-edge + + + + + ## [0.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/quad-edge@0.2.1...@thi.ng/quad-edge@0.2.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/quad-edge diff --git a/packages/quad-edge/package.json b/packages/quad-edge/package.json index 9a674bf348..a94d715618 100644 --- a/packages/quad-edge/package.json +++ b/packages/quad-edge/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/quad-edge", - "version": "0.2.2", + "version": "0.2.3", "description": "Quadedge data structure after Guibas & Stolfi", "module": "./index.js", "main": "./lib/index.js", @@ -29,8 +29,8 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "keywords": [ "dual", diff --git a/packages/random/CHANGELOG.md b/packages/random/CHANGELOG.md index 6e9ed59511..046b028c47 100644 --- a/packages/random/CHANGELOG.md +++ b/packages/random/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/random@1.1.12...@thi.ng/random@1.1.13) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/random + + + + + +## [1.1.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/random@1.1.11...@thi.ng/random@1.1.12) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/random + + + + + +## [1.1.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/random@1.1.10...@thi.ng/random@1.1.11) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/random + + + + + ## [1.1.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/random@1.1.9...@thi.ng/random@1.1.10) (2019-07-31) **Note:** Version bump only for package @thi.ng/random diff --git a/packages/random/package.json b/packages/random/package.json index e49cf211ce..389c0e6ba7 100644 --- a/packages/random/package.json +++ b/packages/random/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/random", - "version": "1.1.10", + "version": "1.1.13", "description": "Pseudo-random number generators w/ unified API", "module": "./index.js", "main": "./lib/index.js", @@ -29,11 +29,11 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2" + "@thi.ng/api": "^6.5.0" }, "keywords": [ "ES6", diff --git a/packages/range-coder/CHANGELOG.md b/packages/range-coder/CHANGELOG.md index 147f04102f..4cb542b8e7 100644 --- a/packages/range-coder/CHANGELOG.md +++ b/packages/range-coder/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/range-coder@1.0.25...@thi.ng/range-coder@1.0.26) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/range-coder + + + + + +## [1.0.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/range-coder@1.0.24...@thi.ng/range-coder@1.0.25) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/range-coder + + + + + +## [1.0.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/range-coder@1.0.23...@thi.ng/range-coder@1.0.24) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/range-coder + + + + + +## [1.0.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/range-coder@1.0.22...@thi.ng/range-coder@1.0.23) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/range-coder + + + + + ## [1.0.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/range-coder@1.0.21...@thi.ng/range-coder@1.0.22) (2019-07-31) **Note:** Version bump only for package @thi.ng/range-coder diff --git a/packages/range-coder/package.json b/packages/range-coder/package.json index ab909cf9d0..3a0a258cee 100644 --- a/packages/range-coder/package.json +++ b/packages/range-coder/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/range-coder", - "version": "1.0.22", + "version": "1.0.26", "description": "Binary data range encoder / decoder", "module": "./index.js", "main": "./lib/index.js", @@ -25,16 +25,16 @@ "pub": "yarn build:release && yarn publish --access public" }, "devDependencies": { - "@thi.ng/transducers": "^5.4.2", + "@thi.ng/transducers": "^6.0.0", "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/bitstream": "^1.1.2" + "@thi.ng/bitstream": "^1.1.4" }, "keywords": [ "ES6", diff --git a/packages/resolve-map/CHANGELOG.md b/packages/resolve-map/CHANGELOG.md index 4acb83873f..b4de0a8bdd 100644 --- a/packages/resolve-map/CHANGELOG.md +++ b/packages/resolve-map/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [4.1.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@4.1.7...@thi.ng/resolve-map@4.1.8) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/resolve-map + + + + + +## [4.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@4.1.6...@thi.ng/resolve-map@4.1.7) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/resolve-map + + + + + +## [4.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@4.1.5...@thi.ng/resolve-map@4.1.6) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/resolve-map + + + + + +## [4.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@4.1.4...@thi.ng/resolve-map@4.1.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/resolve-map + + + + + ## [4.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@4.1.3...@thi.ng/resolve-map@4.1.4) (2019-07-31) **Note:** Version bump only for package @thi.ng/resolve-map diff --git a/packages/resolve-map/package.json b/packages/resolve-map/package.json index d2716afa2d..5843fff454 100644 --- a/packages/resolve-map/package.json +++ b/packages/resolve-map/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/resolve-map", - "version": "4.1.4", + "version": "4.1.8", "description": "DAG resolution of vanilla objects & arrays with internally linked values", "module": "./index.js", "main": "./lib/index.js", @@ -28,14 +28,14 @@ "@types/mocha": "^5.2.6", "@types/node": "^12.6.3", "mocha": "^6.1.4", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/paths": "^2.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/paths": "^2.1.6" }, "keywords": [ "configuration", diff --git a/packages/rle-pack/CHANGELOG.md b/packages/rle-pack/CHANGELOG.md index b9972196ae..0c6c6be608 100644 --- a/packages/rle-pack/CHANGELOG.md +++ b/packages/rle-pack/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rle-pack@2.1.3...@thi.ng/rle-pack@2.1.4) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/rle-pack + + + + + +## [2.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rle-pack@2.1.2...@thi.ng/rle-pack@2.1.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rle-pack + + + + + ## [2.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rle-pack@2.1.1...@thi.ng/rle-pack@2.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/rle-pack diff --git a/packages/rle-pack/README.md b/packages/rle-pack/README.md index ca749ae0c9..557f161b41 100644 --- a/packages/rle-pack/README.md +++ b/packages/rle-pack/README.md @@ -19,7 +19,7 @@ will be encoded using additional RLE chunks... ### Encoding format -![data layout](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rle-layout.png) +![data layout](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rle/rle-layout.png) - 32 bits - original number of words - 5 bits - word size diff --git a/packages/rle-pack/package.json b/packages/rle-pack/package.json index 2065839083..a48e86a843 100644 --- a/packages/rle-pack/package.json +++ b/packages/rle-pack/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rle-pack", - "version": "2.1.2", + "version": "2.1.4", "description": "Binary run-length encoding packer w/ flexible repeat bit widths", "module": "./index.js", "main": "./lib/index.js", @@ -30,12 +30,12 @@ "benchmark": "^2.1.4", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/bitstream": "^1.1.2", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/bitstream": "^1.1.4", + "@thi.ng/errors": "^1.2.1" }, "keywords": [ "binary", diff --git a/packages/router/CHANGELOG.md b/packages/router/CHANGELOG.md index 6a31f3a502..162890ad63 100644 --- a/packages/router/CHANGELOG.md +++ b/packages/router/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.0.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@2.0.5...@thi.ng/router@2.0.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/router + + + + + +## [2.0.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@2.0.4...@thi.ng/router@2.0.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/router + + + + + +## [2.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@2.0.3...@thi.ng/router@2.0.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/router + + + + + +## [2.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@2.0.2...@thi.ng/router@2.0.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/router + + + + + ## [2.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@2.0.1...@thi.ng/router@2.0.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/router diff --git a/packages/router/README.md b/packages/router/README.md index 9827a64cd7..d6b3ad4021 100644 --- a/packages/router/README.md +++ b/packages/router/README.md @@ -56,7 +56,7 @@ A complete, full commented demo app is here: [Live demo](https://demo.thi.ng/umbrella/router-basics/) ```ts -import { HTMLRouter, EV_ROUTE_CHANGED } from "@thi.ng/router"; +import { HTMLRouter, EVENT_ROUTE_CHANGED } from "@thi.ng/router"; // router configuration const config = { @@ -140,7 +140,7 @@ const config = { // `HTMLRouter` ONLY works in browser environments // for non-browser use cases use `BasicRouter` const router = new HTMLRouter(config); -router.addListener(EV_ROUTE_CHANGED, console.log); +router.addListener(EVENT_ROUTE_CHANGED, console.log); router.start(); ``` diff --git a/packages/router/package.json b/packages/router/package.json index 529db1f4f2..b5e40fb512 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/router", - "version": "2.0.2", + "version": "2.0.6", "description": "Generic router for browser & non-browser based applications", "module": "./index.js", "main": "./lib/index.js", @@ -29,14 +29,14 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1" }, "keywords": [ "browser", diff --git a/packages/rstream-csp/CHANGELOG.md b/packages/rstream-csp/CHANGELOG.md index 9b0bc72ab4..257916f3c4 100644 --- a/packages/rstream-csp/CHANGELOG.md +++ b/packages/rstream-csp/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.33](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@1.0.32...@thi.ng/rstream-csp@1.0.33) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/rstream-csp + + + + + +## [1.0.32](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@1.0.31...@thi.ng/rstream-csp@1.0.32) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/rstream-csp + + + + + +## [1.0.31](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@1.0.30...@thi.ng/rstream-csp@1.0.31) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-csp + + + + + +## [1.0.30](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@1.0.29...@thi.ng/rstream-csp@1.0.30) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/rstream-csp + + + + + ## [1.0.29](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@1.0.28...@thi.ng/rstream-csp@1.0.29) (2019-07-31) **Note:** Version bump only for package @thi.ng/rstream-csp diff --git a/packages/rstream-csp/package.json b/packages/rstream-csp/package.json index 01c5ada806..937a93cc5e 100644 --- a/packages/rstream-csp/package.json +++ b/packages/rstream-csp/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-csp", - "version": "1.0.29", + "version": "1.0.33", "description": "@thi.ng/csp bridge module for @thi.ng/rstream", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/csp": "^1.1.2", - "@thi.ng/rstream": "^2.5.2" + "@thi.ng/csp": "^1.1.6", + "@thi.ng/rstream": "^2.5.6" }, "keywords": [ "bridge", diff --git a/packages/rstream-dot/CHANGELOG.md b/packages/rstream-dot/CHANGELOG.md index 4afc961be6..1f83e86017 100644 --- a/packages/rstream-dot/CHANGELOG.md +++ b/packages/rstream-dot/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@1.1.5...@thi.ng/rstream-dot@1.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/rstream-dot + + + + + +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@1.1.4...@thi.ng/rstream-dot@1.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/rstream-dot + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@1.1.3...@thi.ng/rstream-dot@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-dot + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@1.1.2...@thi.ng/rstream-dot@1.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/rstream-dot + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@1.1.1...@thi.ng/rstream-dot@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/rstream-dot diff --git a/packages/rstream-dot/README.md b/packages/rstream-dot/README.md index 979d362cdc..12e4db1ba8 100644 --- a/packages/rstream-dot/README.md +++ b/packages/rstream-dot/README.md @@ -75,7 +75,7 @@ dot -Tsvg -o graph.svg graph.dot This will generate this diagram: -![graphviz output](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rs-dot-example.svg) +![graphviz output](../../assets/examples/rs-dot-example.svg) ## Authors diff --git a/packages/rstream-dot/package.json b/packages/rstream-dot/package.json index 5f9650843e..16ff12e237 100644 --- a/packages/rstream-dot/package.json +++ b/packages/rstream-dot/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-dot", - "version": "1.1.2", + "version": "1.1.6", "description": "Graphviz DOT conversion of @thi.ng/rstream dataflow graph topologies", "module": "./index.js", "main": "./lib/index.js", @@ -29,11 +29,11 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/rstream": "^2.5.2" + "@thi.ng/rstream": "^2.5.6" }, "keywords": [ "conversion", diff --git a/packages/rstream-gestures/CHANGELOG.md b/packages/rstream-gestures/CHANGELOG.md index 83039eead2..b08521ca9f 100644 --- a/packages/rstream-gestures/CHANGELOG.md +++ b/packages/rstream-gestures/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@1.2.5...@thi.ng/rstream-gestures@1.2.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + + + + +## [1.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@1.2.4...@thi.ng/rstream-gestures@1.2.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + + + + +## [1.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@1.2.3...@thi.ng/rstream-gestures@1.2.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + + + + +## [1.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@1.2.2...@thi.ng/rstream-gestures@1.2.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + + + + ## [1.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@1.2.1...@thi.ng/rstream-gestures@1.2.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/rstream-gestures diff --git a/packages/rstream-gestures/README.md b/packages/rstream-gestures/README.md index 001b7678ee..efa19bc421 100644 --- a/packages/rstream-gestures/README.md +++ b/packages/rstream-gestures/README.md @@ -44,7 +44,7 @@ yarn add @thi.ng/rstream-gestures Several small, fully commented projects can be found in the `/examples` folder: -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/canvas-dial.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/canvas-dial.png) [Source](https://github.com/thi-ng/umbrella/tree/master/examples/canvas-dial) | [Live version](https://demo.thi.ng/umbrella/canvas-dial) diff --git a/packages/rstream-gestures/package.json b/packages/rstream-gestures/package.json index a90b7f83bc..22641b9499 100644 --- a/packages/rstream-gestures/package.json +++ b/packages/rstream-gestures/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-gestures", - "version": "1.2.2", + "version": "1.2.6", "description": "Unified mouse, mouse wheel & single-touch event stream abstraction", "module": "./index.js", "main": "./lib/index.js", @@ -29,13 +29,13 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/rstream": "^2.5.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/rstream": "^2.5.6", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "dataflow", diff --git a/packages/rstream-graph/CHANGELOG.md b/packages/rstream-graph/CHANGELOG.md index 435364b214..ab876a1318 100644 --- a/packages/rstream-graph/CHANGELOG.md +++ b/packages/rstream-graph/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.1.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@3.1.7...@thi.ng/rstream-graph@3.1.8) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/rstream-graph + + + + + +## [3.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@3.1.6...@thi.ng/rstream-graph@3.1.7) (2019-09-21) + + +### Bug Fixes + +* **rstream-graph:** const zero input spec handling ([27e9d30](https://github.com/thi-ng/umbrella/commit/27e9d30)) + + + + + +## [3.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@3.1.5...@thi.ng/rstream-graph@3.1.6) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-graph + + + + + +## [3.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@3.1.4...@thi.ng/rstream-graph@3.1.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/rstream-graph + + + + + ## [3.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@3.1.3...@thi.ng/rstream-graph@3.1.4) (2019-07-31) **Note:** Version bump only for package @thi.ng/rstream-graph diff --git a/packages/rstream-graph/README.md b/packages/rstream-graph/README.md index b797abb671..85c35185e4 100644 --- a/packages/rstream-graph/README.md +++ b/packages/rstream-graph/README.md @@ -43,6 +43,9 @@ yarn add @thi.ng/rstream-graph Small(ish), fully commented projects can be found in the `/examples` folder: +- **Spreadsheet w/ Lisp-style DSL** - + [Source](https://github.com/thi-ng/umbrella/tree/master/examples/rstream-spreadsheet), + [Live version](https://demo.thi.ng/umbrella/rstream-spreadsheet) - **SVG grid gen** - [Source](https://github.com/thi-ng/umbrella/tree/master/examples/rstream-grid), [Live version](https://demo.thi.ng/umbrella/rstream-grid) diff --git a/packages/rstream-graph/package.json b/packages/rstream-graph/package.json index 1c38baa62d..77f09af953 100644 --- a/packages/rstream-graph/package.json +++ b/packages/rstream-graph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-graph", - "version": "3.1.4", + "version": "3.1.8", "description": "Declarative dataflow graph construction for @thi.ng/rstream", "module": "./index.js", "main": "./lib/index.js", @@ -29,17 +29,17 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/paths": "^2.1.2", - "@thi.ng/resolve-map": "^4.1.4", - "@thi.ng/rstream": "^2.5.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/paths": "^2.1.6", + "@thi.ng/resolve-map": "^4.1.8", + "@thi.ng/rstream": "^2.5.6", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "compute", diff --git a/packages/rstream-graph/src/graph.ts b/packages/rstream-graph/src/graph.ts index e2e7a8c38e..35f74d3e9a 100644 --- a/packages/rstream-graph/src/graph.ts +++ b/packages/rstream-graph/src/graph.ts @@ -116,7 +116,7 @@ const prepareNodeInputs = ( s = fromView(state, i.path); } else if (i.stream) { s = isString(i.stream) ? resolve(i.stream) : i.stream(resolve); - } else if (i.const) { + } else if (i.const != null) { s = fromIterableSync( [isFunction(i.const) ? i.const(resolve) : i.const], false diff --git a/packages/rstream-log-file/CHANGELOG.md b/packages/rstream-log-file/CHANGELOG.md index 8db9c7aff2..dfa1ae074f 100644 --- a/packages/rstream-log-file/CHANGELOG.md +++ b/packages/rstream-log-file/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log-file@0.1.20...@thi.ng/rstream-log-file@0.1.21) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/rstream-log-file + + + + + +## [0.1.20](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log-file@0.1.19...@thi.ng/rstream-log-file@0.1.20) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/rstream-log-file + + + + + +## [0.1.19](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log-file@0.1.18...@thi.ng/rstream-log-file@0.1.19) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-log-file + + + + + +## [0.1.18](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log-file@0.1.17...@thi.ng/rstream-log-file@0.1.18) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/rstream-log-file + + + + + ## [0.1.17](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log-file@0.1.16...@thi.ng/rstream-log-file@0.1.17) (2019-07-31) **Note:** Version bump only for package @thi.ng/rstream-log-file diff --git a/packages/rstream-log-file/package.json b/packages/rstream-log-file/package.json index 0e0300e783..8039ea491c 100644 --- a/packages/rstream-log-file/package.json +++ b/packages/rstream-log-file/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-log-file", - "version": "0.1.17", + "version": "0.1.21", "description": "File output handler for structured, multilevel & hierarchical loggers based on @thi.ng/rstream", "module": "./index.js", "main": "./lib/index.js", @@ -29,11 +29,11 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/rstream": "^2.5.2" + "@thi.ng/rstream": "^2.5.6" }, "keywords": [ "append", diff --git a/packages/rstream-log/CHANGELOG.md b/packages/rstream-log/CHANGELOG.md index 9faea7ba5c..6111623d5d 100644 --- a/packages/rstream-log/CHANGELOG.md +++ b/packages/rstream-log/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@3.1.5...@thi.ng/rstream-log@3.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/rstream-log + + + + + +## [3.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@3.1.4...@thi.ng/rstream-log@3.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/rstream-log + + + + + +## [3.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@3.1.3...@thi.ng/rstream-log@3.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-log + + + + + +## [3.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@3.1.2...@thi.ng/rstream-log@3.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/rstream-log + + + + + ## [3.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@3.1.1...@thi.ng/rstream-log@3.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/rstream-log diff --git a/packages/rstream-log/package.json b/packages/rstream-log/package.json index b6947eee32..04553f01cb 100644 --- a/packages/rstream-log/package.json +++ b/packages/rstream-log/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-log", - "version": "3.1.2", + "version": "3.1.6", "description": "Structured, multilevel & hierarchical loggers based on @thi.ng/rstream", "module": "./index.js", "main": "./lib/index.js", @@ -29,15 +29,15 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/rstream": "^2.5.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/rstream": "^2.5.6", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "ES6", diff --git a/packages/rstream-query/CHANGELOG.md b/packages/rstream-query/CHANGELOG.md index ba008f97f6..270b0f3814 100644 --- a/packages/rstream-query/CHANGELOG.md +++ b/packages/rstream-query/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@1.1.5...@thi.ng/rstream-query@1.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/rstream-query + + + + + +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@1.1.4...@thi.ng/rstream-query@1.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/rstream-query + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@1.1.3...@thi.ng/rstream-query@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream-query + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@1.1.2...@thi.ng/rstream-query@1.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/rstream-query + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@1.1.1...@thi.ng/rstream-query@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/rstream-query diff --git a/packages/rstream-query/README.md b/packages/rstream-query/README.md index 1f3c927fe0..1ee0130838 100644 --- a/packages/rstream-query/README.md +++ b/packages/rstream-query/README.md @@ -162,7 +162,7 @@ addCity("paris", "france"); After setting up the above query and its internal transformations, the generated dataflow topology then looks as follows: -![graphviz output](../../assets/rs-query1.svg) +![graphviz output](../../assets/examples/rs-query1.svg) - The blue nodes are `TripleStore`-internal index stream sources, emitting changes when new triples are added diff --git a/packages/rstream-query/package.json b/packages/rstream-query/package.json index a9e97518e1..fa2f509e90 100644 --- a/packages/rstream-query/package.json +++ b/packages/rstream-query/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-query", - "version": "1.1.2", + "version": "1.1.6", "description": "@thi.ng/rstream based triple store & reactive query engine", "module": "./index.js", "main": "./lib/index.js", @@ -29,18 +29,19 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/associative": "^2.4.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/rstream": "^2.5.2", - "@thi.ng/rstream-dot": "^1.1.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/associative": "^3.1.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/math": "^1.5.0", + "@thi.ng/rstream": "^2.5.6", + "@thi.ng/rstream-dot": "^1.1.6", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "dataflow", diff --git a/packages/rstream-query/src/store.ts b/packages/rstream-query/src/store.ts index 0e44fdd1d3..7c9478cfc8 100644 --- a/packages/rstream-query/src/store.ts +++ b/packages/rstream-query/src/store.ts @@ -1,7 +1,8 @@ import { assert, IObjectOf } from "@thi.ng/api"; -import { intersection, join } from "@thi.ng/associative"; +import { join } from "@thi.ng/associative"; import { equiv } from "@thi.ng/equiv"; import { illegalArgs } from "@thi.ng/errors"; +import { min3id } from "@thi.ng/math"; import { ISubscribable, nextID, @@ -18,19 +19,14 @@ import { import { assocObj, comp, - compR, dedupe, - keySelector, map, mapIndexed, - Reducer, transduce, Transducer } from "@thi.ng/transducers"; import { - BindFn, Edit, - LOGGER, PathPattern, PathQuerySpec, Pattern, @@ -45,6 +41,16 @@ import { } from "./api"; import { patternVars, resolvePathPattern } from "./pattern"; import { isQVar, qvarResolver } from "./qvar"; +import { + bindVars, + filterSolutions, + indexSel, + intersect2, + intersect3, + joinSolutions, + limitSolutions, + resultTriples +} from "./xforms"; export class TripleStore implements Iterable, IToDot { NEXT_ID: number; @@ -361,24 +367,17 @@ export class TripleStore implements Iterable, IToDot { for (let q of spec.q) { if (isWhereQuery(q)) { curr = this.addMultiJoin(this.addParamQueries(q.where)); - query && (curr = this.addJoin(query, curr)); } else if (isPathQuery(q)) { curr = this.addPathQuery(q.path); - query && (curr = this.addJoin(query, curr)); } + query && curr && (curr = this.addJoin(query, curr)); query = curr; } assert(!!query, "illegal query spec"); let xforms: Transducer[] = []; - if (spec.limit) { - xforms.push(limitSolutions(spec.limit)); - } - if (spec.bind) { - xforms.push(bindVars(spec.bind)); - } - if (spec.select) { - xforms.push(filterSolutions(spec.select)); - } + spec.limit && xforms.push(limitSolutions(spec.limit)); + spec.bind && xforms.push(bindVars(spec.bind)); + spec.select && xforms.push(filterSolutions(spec.select)); if (xforms.length) { query = >( query!.subscribe(comp.apply(null, xforms)) @@ -398,10 +397,7 @@ export class TripleStore implements Iterable, IToDot { } protected nextID() { - if (this.freeIDs.length) { - return this.freeIDs.pop()!; - } - return this.NEXT_ID++; + return this.freeIDs.length ? this.freeIDs.pop()! : this.NEXT_ID++; } private broadcastTriple( @@ -424,18 +420,7 @@ export class TripleStore implements Iterable, IToDot { ) { if (s && p && o) { const triples = this.triples; - const index = - s.size < p.size - ? s.size < o.size - ? s - : p.size < o.size - ? p - : o - : p.size < o.size - ? p - : s.size < o.size - ? s - : o; + const index = [s, p, o][min3id(s.size, p.size, o.size)]; for (let id of index) { if (equiv(triples[id], f)) { return id; @@ -482,83 +467,6 @@ const submit = ( } }; -const intersect2: Transducer, TripleIds> = comp( - map(({ a, b }) => intersection(a, b)), - dedupe(equiv) -); - -const intersect3: Transducer, TripleIds> = comp( - map(({ s, p, o }) => intersection(intersection(s, p), o)), - dedupe(equiv) -); - -const indexSel = (key: any): Transducer => ( - rfn: Reducer -) => { - const r = rfn[2]; - return compR(rfn, (acc, e) => { - LOGGER.fine("index sel", e.key, key); - if (equiv(e.key, key)) { - return r(acc, e.index); - } - return acc; - }); -}; - -const resultTriples = (graph: TripleStore) => - map>((ids) => { - const res = new Set(); - for (let id of ids) res.add(graph.triples[id]); - return res; - }); - -const joinSolutions = (n: number) => - map, Solutions>((src) => { - let res: Solutions = src[0]; - for (let i = 1; i < n && res.size; i++) { - res = join(res, src[i]); - } - return res; - }); - -const filterSolutions = (qvars: Iterable) => { - const filterVars = keySelector([...qvars]); - return map((sol: Solutions) => { - const res: Solutions = new Set(); - for (let s of sol) { - res.add(filterVars(s)); - } - return res; - }); -}; - -const limitSolutions = (n: number) => - map((sol: Solutions) => { - if (sol.size <= n) { - return sol; - } - const res: Solutions = new Set(); - let m = n; - for (let s of sol) { - res.add(s); - if (--m <= 0) break; - } - return res; - }); - -const bindVars = (bindings: IObjectOf) => - map((sol: Solutions) => { - const res: Solutions = new Set(); - for (let s of sol) { - s = { ...s }; - res.add(s); - for (let b in bindings) { - s[b] = bindings[b](s); - } - } - return res; - }); - const isWhereQuery = (q: SubQuerySpec): q is WhereQuerySpec => !!(q).where; const isPathQuery = (q: SubQuerySpec): q is PathQuerySpec => !!(q).path; diff --git a/packages/rstream-query/src/xforms.ts b/packages/rstream-query/src/xforms.ts new file mode 100644 index 0000000000..f66fce3cf7 --- /dev/null +++ b/packages/rstream-query/src/xforms.ts @@ -0,0 +1,98 @@ +import { IObjectOf } from "@thi.ng/api"; +import { intersection, join } from "@thi.ng/associative"; +import { equiv } from "@thi.ng/equiv"; +import { LOGGER } from "@thi.ng/rstream"; +import { + comp, + compR, + dedupe, + keySelector, + map, + Reducer, + Transducer +} from "@thi.ng/transducers"; +import { + BindFn, + Edit, + Solutions, + Triple, + TripleIds +} from "./api"; +import { TripleStore } from "./store"; + +export const intersect2: Transducer, TripleIds> = comp( + map(({ a, b }) => intersection(a, b)), + dedupe(equiv) +); + +export const intersect3: Transducer, TripleIds> = comp( + map(({ s, p, o }) => intersection(intersection(s, p), o)), + dedupe(equiv) +); + +export const indexSel = (key: any): Transducer => ( + rfn: Reducer +) => { + const r = rfn[2]; + return compR(rfn, (acc, e) => { + LOGGER.fine("index sel", e.key, key); + if (equiv(e.key, key)) { + return r(acc, e.index); + } + return acc; + }); +}; + +export const resultTriples = (graph: TripleStore) => + map>((ids) => { + const res = new Set(); + for (let id of ids) res.add(graph.triples[id]); + return res; + }); + +export const joinSolutions = (n: number) => + map, Solutions>((src) => { + let res: Solutions = src[0]; + for (let i = 1; i < n && res.size; i++) { + res = join(res, src[i]); + } + return res; + }); + +export const filterSolutions = (qvars: Iterable) => { + const filterVars = keySelector([...qvars]); + return map((sol: Solutions) => { + const res: Solutions = new Set(); + for (let s of sol) { + res.add(filterVars(s)); + } + return res; + }); +}; + +export const limitSolutions = (n: number) => + map((sol: Solutions) => { + if (sol.size <= n) { + return sol; + } + const res: Solutions = new Set(); + let m = n; + for (let s of sol) { + res.add(s); + if (--m <= 0) break; + } + return res; + }); + +export const bindVars = (bindings: IObjectOf) => + map((sol: Solutions) => { + const res: Solutions = new Set(); + for (let s of sol) { + s = { ...s }; + res.add(s); + for (let b in bindings) { + s[b] = bindings[b](s); + } + } + return res; + }); diff --git a/packages/rstream/CHANGELOG.md b/packages/rstream/CHANGELOG.md index 3a09a07c9b..05159cf747 100644 --- a/packages/rstream/CHANGELOG.md +++ b/packages/rstream/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.5.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@2.5.5...@thi.ng/rstream@2.5.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/rstream + + + + + +## [2.5.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@2.5.4...@thi.ng/rstream@2.5.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/rstream + + + + + +## [2.5.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@2.5.3...@thi.ng/rstream@2.5.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/rstream + + + + + +## [2.5.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@2.5.2...@thi.ng/rstream@2.5.3) (2019-08-16) + + +### Bug Fixes + +* **rstream:** preserve const enums ([765a9ac](https://github.com/thi-ng/umbrella/commit/765a9ac)) + + + + + ## [2.5.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@2.5.1...@thi.ng/rstream@2.5.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/rstream diff --git a/packages/rstream/README.md b/packages/rstream/README.md index aee0c0cfc9..d258292e4d 100644 --- a/packages/rstream/README.md +++ b/packages/rstream/README.md @@ -9,28 +9,28 @@ This project is part of the -- [About](#about) -- [Support packages](#support-packages) -- [Conceptual differences to RxJS](#conceptual-differences-to-rxjs) -- [Installation](#installation) -- [Dependencies](#dependencies) -- [Usage examples](#usage-examples) - - [Realtime crypto candle chart](#realtime-crypto-candle-chart) - - [Worker-based mandelbrot fractal renderer](#worker-based-mandelbrot-fractal-renderer) - - [Interactive SVG grid generator](#interactive-svg-grid-generator) - - [Mouse gesture analysis](#mouse-gesture-analysis) - - [Declarative dataflow graph](#declarative-dataflow-graph) - - [@thi.ng/hdom benchmark](#thinghdom-benchmark) -- [API](#api) - - [Stream creation](#stream-creation) - - [Meta streams](#meta-streams) - - [Stream merging](#stream-merging) - - [Stream splitting](#stream-splitting) - - [Side-chaining](#side-chaining) - - [Worker support](#worker-support) - - [Other subscription ops](#other-subscription-ops) -- [Authors](#authors) -- [License](#license) +- [About](#about) +- [Support packages](#support-packages) +- [Conceptual differences to RxJS](#conceptual-differences-to-rxjs) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) + - [Realtime crypto candle chart](#realtime-crypto-candle-chart) + - [Worker-based mandelbrot fractal renderer](#worker-based-mandelbrot-fractal-renderer) + - [Interactive SVG grid generator](#interactive-svg-grid-generator) + - [Mouse gesture analysis](#mouse-gesture-analysis) + - [Declarative dataflow graph](#declarative-dataflow-graph) + - [@thi.ng/hdom benchmark](#thinghdom-benchmark) +- [API](#api) + - [Stream creation](#stream-creation) + - [Meta streams](#meta-streams) + - [Stream merging](#stream-merging) + - [Stream splitting](#stream-splitting) + - [Side-chaining](#side-chaining) + - [Worker support](#worker-support) + - [Other subscription ops](#other-subscription-ops) +- [Authors](#authors) +- [License](#license) @@ -117,28 +117,28 @@ A small selection: ### Realtime crypto candle chart -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/crypto-chart.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/crypto-chart.png) [Source](https://github.com/thi-ng/umbrella/tree/master/examples/crypto-chart) | [Live version](https://demo.thi.ng/umbrella/crypto-chart/) ### Worker-based mandelbrot fractal renderer -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/mandelbrot.jpg) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/mandelbrot.jpg) [Source](https://github.com/thi-ng/umbrella/tree/master/examples/mandelbrot) | [Live version](https://demo.thi.ng/umbrella/mandelbrot/) ### Interactive SVG grid generator -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/rstream-grid.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/rstream-grid.png) [Source](https://github.com/thi-ng/umbrella/tree/master/examples/rstream-grid) | [Live version](https://demo.thi.ng/umbrella/rstream-grid/) ### Mouse gesture analysis -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/gesture-analysis.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/gesture-analysis.png) [Source](https://github.com/thi-ng/umbrella/tree/master/examples/gesture-analysis) | [Live version](https://demo.thi.ng/umbrella/gesture-analysis) @@ -334,7 +334,7 @@ m.next(true); #### [merge()](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/stream-merge.ts) - unsorted merge from multiple inputs (dynamic add/remove) -![diagram](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rstream-merge.png) +![diagram](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rstream/rstream-merge.png) Returns a new `StreamMerge` instance, a subscription type consuming inputs from multiple inputs and passing received values on to any @@ -417,7 +417,7 @@ a.next("a"); #### [sync()](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/stream-sync.ts) - synchronized merge and labeled tuple objects -![diagram](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rstream-sync.png) +![diagram](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rstream/rstream-sync.png) Similar to `StreamMerge` above, but with extra synchronization of inputs. Before emitting any new values, `StreamSync` collects values @@ -468,7 +468,7 @@ for further reference of the various behavior options. #### [pubsub()](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/pubsub.ts) - topic based splitting -![diagram](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rstream-pubsub.png) +![diagram](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rstream/rstream-pubsub.png) Topic based stream splitter. Applies `topic` function to each received value and only forwards it to child subscriptions for @@ -534,7 +534,7 @@ rs.fromIterable([1, 2, 3, 4]).subscribe(rs.bisect((x) => !!(x & 1), odd, even)); #### [sidechainPartition()](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/subs/sidechain-partition.ts) - chunks input, controlled by sidechain -![diagram](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rstream-sidechain-partition.png) +![diagram](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rstream/rstream-sidechain-partition.png) Buffers values from `src` until side chain fires, then emits buffer (unless empty) and repeats process until either input is done. By @@ -557,7 +557,7 @@ merge([ #### [sidechainToggle()](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/subs/sidechain-toggle.ts) - toggles input, controlled by sidechain -![diagram](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rstream-sidechain-toggle.png) +![diagram](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/rstream/rstream-sidechain-toggle.png) Filters values from input based on values received from side chain. By default, the value read from the side chain is ignored, however the diff --git a/packages/rstream/package.json b/packages/rstream/package.json index f3941bb733..ce4f8b8093 100644 --- a/packages/rstream/package.json +++ b/packages/rstream/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream", - "version": "2.5.2", + "version": "2.5.6", "description": "Reactive multi-tap streams, dataflow & transformation pipeline constructs", "module": "./index.js", "main": "./lib/index.js", @@ -29,17 +29,17 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/associative": "^2.4.2", - "@thi.ng/atom": "^3.0.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/paths": "^2.1.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/associative": "^3.1.0", + "@thi.ng/atom": "^3.1.1", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/paths": "^2.1.6", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "datastructure", diff --git a/packages/rstream/tsconfig.json b/packages/rstream/tsconfig.json index b445790069..fbd44a729e 100644 --- a/packages/rstream/tsconfig.json +++ b/packages/rstream/tsconfig.json @@ -4,7 +4,7 @@ "outDir": ".", "module": "es6", "target": "es6", - "preserveConstEnums": false + "preserveConstEnums": true }, "include": ["./src/**/*.ts"] } diff --git a/packages/sax/CHANGELOG.md b/packages/sax/CHANGELOG.md index 6f2ccf12e7..21c1d8272a 100644 --- a/packages/sax/CHANGELOG.md +++ b/packages/sax/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/sax@1.1.5...@thi.ng/sax@1.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/sax + + + + + +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/sax@1.1.4...@thi.ng/sax@1.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/sax + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/sax@1.1.3...@thi.ng/sax@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/sax + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/sax@1.1.2...@thi.ng/sax@1.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/sax + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/sax@1.1.1...@thi.ng/sax@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/sax diff --git a/packages/sax/package.json b/packages/sax/package.json index 242c22ce69..5060329c97 100644 --- a/packages/sax/package.json +++ b/packages/sax/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/sax", - "version": "1.1.2", + "version": "1.1.6", "description": "Transducer-based, SAX-like, non-validating, speedy & tiny XML parser", "module": "./index.js", "main": "./lib/index.js", @@ -29,13 +29,13 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/transducers-fsm": "^1.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/transducers-fsm": "^1.1.6" }, "keywords": [ "ES6", diff --git a/packages/sax/src/index.ts b/packages/sax/src/index.ts index d16b02bdd1..c7b061a8aa 100644 --- a/packages/sax/src/index.ts +++ b/packages/sax/src/index.ts @@ -269,20 +269,7 @@ const PARSER: FSMStateMap = { } else if (isWS(ch)) { state.state = State.MAYBE_ATTRIB; } else if (ch === ">") { - state.state = State.ELEM_BODY; - state.scope.push({ - tag: state.tag, - attribs: state.attribs, - children: [] - }); - state.body = ""; - return [ - { - type: Type.ELEM_START, - tag: state.tag, - attribs: state.attribs - } - ]; + return beginElementBody(state); } else if (ch === "/") { state.state = State.ELEM_SINGLE; } else { diff --git a/packages/sexpr/.npmignore b/packages/sexpr/.npmignore new file mode 100644 index 0000000000..24f388daa6 --- /dev/null +++ b/packages/sexpr/.npmignore @@ -0,0 +1,18 @@ +.cache +.meta +.nyc_output +*.gz +*.html +*.svg +*.tgz +*.h +*.o +*.wasm +build +coverage +dev +doc +export +src* +test +tsconfig.json diff --git a/packages/sexpr/CHANGELOG.md b/packages/sexpr/CHANGELOG.md new file mode 100644 index 0000000000..37e6a5616f --- /dev/null +++ b/packages/sexpr/CHANGELOG.md @@ -0,0 +1,32 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/sexpr@0.2.0...@thi.ng/sexpr@0.2.1) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/sexpr + + + + + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/sexpr@0.1.0...@thi.ng/sexpr@0.2.0) (2019-09-23) + + +### Features + +* **sexpr:** add Token w/ location info, update tokenize() & parse() ([3917775](https://github.com/thi-ng/umbrella/commit/3917775)) + + + + + +# 0.1.0 (2019-09-21) + + +### Features + +* **sexpr:** add ParseError ([7998afe](https://github.com/thi-ng/umbrella/commit/7998afe)) +* **sexpr:** import as new package ([f526b7c](https://github.com/thi-ng/umbrella/commit/f526b7c)) +* **sexpr:** update SyntaxOpts, runtime, update scope parsing, types ([7c840e1](https://github.com/thi-ng/umbrella/commit/7c840e1)) diff --git a/packages/sexpr/LICENSE b/packages/sexpr/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/sexpr/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/sexpr/README.md b/packages/sexpr/README.md new file mode 100644 index 0000000000..bd2eed26aa --- /dev/null +++ b/packages/sexpr/README.md @@ -0,0 +1,267 @@ +# @thi.ng/sexpr + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/sexpr.svg)](https://www.npmjs.com/package/@thi.ng/sexpr) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/sexpr.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + +- [About](#about) +- [Status](#status) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) + - [Tokenize only (iterator)](#tokenize-only-iterator) + - [AST generation](#ast-generation) + - [Interpreter](#interpreter) + - [Custom syntax](#custom-syntax) +- [Authors](#authors) +- [License](#license) + + + +## About + +Basic, but configurable and extensible +[S-Expression](https://en.wikipedia.org/wiki/S-expression) tokenizer, +parser, AST builder and runtime / interpreter skeleton for custom, +sandboxed DSL implementations. + +The following default syntax rules are used: + +- **whitespace**: space, tab, newline, comma +- **expression delimiters**: `(`, `)` +- **numbers**: any float notation valid in JS, hex ints prefixed w/ `0x` +- **string delimiters**: `"` + +Everything else is parsed as is, i.e. as symbol. + +## Status + +ALPHA + +## Installation + +```bash +yarn add @thi.ng/sexpr +``` + +## Dependencies + +- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) +- [@thi.ng/defmulti](https://github.com/thi-ng/umbrella/tree/master/packages/defmulti) + +## Usage examples + +- [DSL for spreadsheet formulas](https://github.com/thi-ng/umbrella/tree/master/examples/rstream-spreadsheet) + +```ts +import { tokenize, parse, runtime } from "@thi.ng/sexpr"; +``` + +### Tokenize only (iterator) + +The `tokenize` function returns an iterator of tokens incl. location +details. Any whitespace is skipped and whitespace characters are +configurable. + +```ts +[...tokenize(`(* (+ 3 5) 10)`)]; +// [ +// { value: '(', line: 0, col: 0 }, +// { value: '*', line: 0, col: 1 }, +// { value: '(', line: 0, col: 3 }, +// { value: '+', line: 0, col: 4 }, +// { value: '3', line: 0, col: 6 }, +// { value: '5', line: 0, col: 8 }, +// { value: ')', line: 0, col: 9 }, +// { value: '10', line: 0, col: 11 }, +// { value: ')', line: 0, col: 13 } +// ] +``` + +### AST generation + +The `parse` function takes a source string or iterable of tokens and +parses it into an AST. + +```ts +parse(tokenize(`(* (+ 3 5) 10)`)); +// or directly from string +parse(`(* (+ 3 5) 10)`); +``` + +```json +{ + "type": "root", + "children": [ + { + "type": "expr", + "value": "(", + "children": [ + { + "type": "sym", + "value": "*" + }, + { + "type": "expr", + "value": "(", + "children": [ + { + "type": "sym", + "value": "+" + }, + { + "type": "num", + "value": 3 + }, + { + "type": "num", + "value": 5 + } + ] + }, + { + "type": "num", + "value": 10 + } + ] + } + ] +} +``` + +### Interpreter + +```ts +import { Fn2 } from "@thi.ng/api"; +import { defmulti, DEFAULT } from "@thi.ng/defmulti"; +import { ASTNode, Implementations, Sym } from "@thi.ng/sexpr"; + +// multi-dispatch fn for DSL builtins +// we will call this function for each S-expression +// and will delegate to the actual implementation based on +// the expression's first item (a symbol name) +const builtins = defmulti((x) => x.value); + +// build runtime w/ impls for all AST node types +// the generics are the types of: the custom environment (if used) +// and the result type(s) +const rt = runtime, any, any>({ + // delegate to builtins + expr: (x, env) => builtins(x.children[0], x.children, env), + // lookup symbol in environment + sym: (x, env) => env[x.value], + // strings and numbers evaluate verbatim + str: (x) => x.value, + num: (x) => x.value +}); + +// helper HOF for math ops +const op = (fn: Fn2) => + (_: ASTNode, vals: ASTNode[], env: any) => + vals.slice(2).reduce( + (acc, x) => fn(acc, rt(x, env)), + rt(vals[1], env) + ); + +// add builtins +builtins.addAll({ + "+": op((acc, x) => acc + x), + "*": op((acc, x) => acc * x), + "-": op((acc, x) => acc - x), + "/": op((acc, x) => acc / x), + count: (_, [__, x]) => rt(x).length +}); + +// add default/fallback implementation +// to allow calling functions stored in environment +builtins.add(DEFAULT, (x, [_, ...args], env) => { + const f = env[(x).value]; + assert(!!f, "missing impl"); + return f.apply(null, args.map((a) => rt(a, env))); +}); + +// evaluator +const $eval = (src: string, env: any = {}) => + rt(parse(src).children[0], env); + +// evaluate expression w/ given env bindings +$eval(`(* foo (+ 1 2 3 (count "abcd")))`, { foo: 10 }); +// 100 +// i.e. 100 = 10 * (1 + 2 + 3 + 4) + +// call env function +$eval( + `(join (+ 1 2) (* 3 4))`, + { join: (...xs: any[]) => xs.join(",") } +); +// "3,12" +``` + +See +[test/](https://github.com/thi-ng/umbrella/tree/master/packages/sexpr/test/) +for a more in-depth version of this example... + +### Custom syntax + +```ts +// define syntax overrides (keep default whitespace rules) +const syntax = { + scopes: [["<", ">"], ["{", "}"]], + string: "'" +}; + +parse(``, syntax); +``` + +```json +{ + "type": "root", + "children": [ + { + "type": "expr", + "value": "<", + "children": [ + { + "type": "sym", + "value": "nest" + }, + { + "type": "expr", + "value": "{", + "children": [ + { + "type": "sym", + "value": "a" + }, + { + "type": "str", + "value": "2" + }, + { + "type": "sym", + "value": "b" + }, + { + "type": "num", + "value": 3 + } + ] + } + ] + } + ] +} +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2016 - 2019 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/sexpr/package.json b/packages/sexpr/package.json new file mode 100644 index 0000000000..2628b2908f --- /dev/null +++ b/packages/sexpr/package.json @@ -0,0 +1,56 @@ +{ + "name": "@thi.ng/sexpr", + "version": "0.2.1", + "description": "Extensible S-Expression parser & runtime infrastructure", + "module": "./index.js", + "main": "./lib/index.js", + "umd:main": "./lib/index.umd.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/sexpr", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module", + "build:release": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module all", + "build:es6": "tsc --declaration", + "build:test": "rimraf build && tsc -p test/tsconfig.json", + "test": "yarn build:test && mocha build/test/*.js", + "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib", + "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", + "pub": "yarn build:release && yarn publish --access public" + }, + "devDependencies": { + "@types/mocha": "^5.2.6", + "@types/node": "^12.6.3", + "mocha": "^6.1.4", + "nyc": "^14.0.0", + "typedoc": "^0.15.0", + "typescript": "^3.6.4" + }, + "dependencies": { + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/defmulti": "^1.2.0" + }, + "keywords": [ + "DSL", + "ES6", + "interpreter", + "lisp", + "parser", + "runtime", + "s-expression", + "tokenizer", + "typescript", + "WAST" + ], + "publishConfig": { + "access": "public" + }, + "sideEffects": false +} diff --git a/packages/sexpr/src/api.ts b/packages/sexpr/src/api.ts new file mode 100644 index 0000000000..a4c36a1460 --- /dev/null +++ b/packages/sexpr/src/api.ts @@ -0,0 +1,110 @@ +import { Fn2 } from "@thi.ng/api"; + +export interface SyntaxOpts { + /** + * An array of string pairs defining possible S-expression / scope + * delimiters [open, close]. By default only the classic ["(",")"] + * pair is defined. Only single character delimiters are supported. + * + * @see DEFAULT_SYNTAX + */ + scopes: string[][]; + /** + * Regex to identify whitespace a single whitespace character. + * Default: space, tab, newline, comma + */ + whiteSpace: RegExp; + /** + * Single character string to delineate string values. + * Default: `"` + */ + string: string; +} + +export interface Token { + /** + * Token value + */ + value: string; + /** + * Token's start line + */ + line: number; + /** + * Token's start column + */ + col: number; +} + +export type NodeType = "root" | "expr" | "sym" | "str" | "num"; + +export type ASTNode = Root | Expression | Sym | Str | Numeric; + +/** + * Base interface for custom AST nodes + */ +export interface INode { + type: string; + parent?: INode; +} + +export interface Root extends INode { + type: "root"; + children: ASTNode[]; +} + +/** + * AST Node defining an S-expression. + */ +export interface Expression extends INode { + type: "expr"; + /** + * Child nodes + */ + children: ASTNode[]; + /** + * Scope type char (as per configured syntax) + */ + value: string; +} + +/** + * AST symbol node. Merely holds symbol name. + */ +export interface Sym extends INode { + type: "sym"; + value: string; +} + +/** + * AST string node. Merely wraps string value. + */ +export interface Str extends INode { + type: "str"; + value: string; +} + +/** + * AST numeric node. Merely wraps parsed numeric value. + */ +export interface Numeric extends INode { + type: "num"; + value: number; +} + +/** + * Type hinted runtime implementations. + */ +export interface Implementations { + root: Fn2; + expr: Fn2; + sym: Fn2; + str: Fn2; + num: Fn2; +} + +export const DEFAULT_SYNTAX: SyntaxOpts = { + scopes: [["(", ")"]], + whiteSpace: /(\s|,)/, + string: '"' +}; diff --git a/packages/sexpr/src/index.ts b/packages/sexpr/src/index.ts new file mode 100644 index 0000000000..bc454ae03f --- /dev/null +++ b/packages/sexpr/src/index.ts @@ -0,0 +1,4 @@ +export * from "./api"; +export * from "./parse"; +export * from "./runtime"; +export * from "./tokenize"; diff --git a/packages/sexpr/src/parse.ts b/packages/sexpr/src/parse.ts new file mode 100644 index 0000000000..78880dac19 --- /dev/null +++ b/packages/sexpr/src/parse.ts @@ -0,0 +1,79 @@ +import { isString } from "@thi.ng/checks"; +import { + ASTNode, + DEFAULT_SYNTAX, + Expression, + Root, + SyntaxOpts, + Token +} from "./api"; +import { tokenize } from "./tokenize"; + +class ParseError extends Error { + line: number; + col: number; + + constructor(msg: string, line: number, col: number) { + super(msg); + this.line = line; + this.col = col; + } +} + +/** + * Takes a `src` string or `Token` iteratable and parses it into an AST, + * then returns tree's root node. Throws `ParserError` if the token + * order causes illegal nesting. The error includes `line` and `column` + * information of the offending token. + * + * @param src + * @param opts + */ +export const parse = ( + src: string | Iterable, + opts?: Partial +) => { + const { scopes } = { + ...DEFAULT_SYNTAX, + ...opts + }; + const scopeOpen = scopes.map((x) => x[0]); + const scopeClose = scopes.map((x) => x[1]); + const tree: ASTNode[] = [{ type: "root", children: [] }]; + let currScope = -1; + for (let token of isString(src) ? tokenize(src, opts) : src) { + const t = token.value; + let tmp: number; + if ((tmp = scopeOpen.indexOf(t)) !== -1) { + tree.push({ type: "expr", value: t, children: [] }); + currScope = tmp; + } else if ((tmp = scopeClose.indexOf(t)) !== -1) { + if (tree.length < 2 || currScope !== tmp) { + throw new ParseError(`unmatched '${t}'`, token.line, token.col); + } + (tree[tree.length - 2]).children!.push(tree.pop()!); + currScope = scopeOpen.indexOf( + (tree[tree.length - 1]).value + ); + } else { + let node: ASTNode; + let value: number; + if (t.startsWith('"')) { + node = { type: "str", value: t.substring(1, t.length - 1) }; + } else if ( + (t.startsWith("0x") && + !isNaN((value = parseInt(t.substr(2), 16)))) || + !isNaN((value = parseFloat(t))) + ) { + node = { type: "num", value }; + } else { + node = { type: "sym", value: t }; + } + (tree[tree.length - 1]).children!.push(node); + } + } + if (tree.length > 1) { + throw new ParseError("unclosed s-expression", -1, -1); + } + return tree[0]; +}; diff --git a/packages/sexpr/src/runtime.ts b/packages/sexpr/src/runtime.ts new file mode 100644 index 0000000000..1339a517ee --- /dev/null +++ b/packages/sexpr/src/runtime.ts @@ -0,0 +1,10 @@ +import { defmulti, MultiFn1O } from "@thi.ng/defmulti"; +import { ASTNode, Implementations } from "./api"; + +export const runtime = , ENV, RES>( + impls: Partial +) => { + const rt: MultiFn1O = defmulti((x: ASTNode) => x.type); + rt.addAll(impls); + return rt; +}; diff --git a/packages/sexpr/src/tokenize.ts b/packages/sexpr/src/tokenize.ts new file mode 100644 index 0000000000..dd71079109 --- /dev/null +++ b/packages/sexpr/src/tokenize.ts @@ -0,0 +1,74 @@ +import { DEFAULT_SYNTAX, SyntaxOpts, Token } from "./api"; + +/** + * Yields iterator of `Token`s (incl. location info) from `src` string + * (or from a **characterwise** iterable). Scope and string delimiters + * and whitespace characters can be configured via given `opts`. By + * default `DEFAULT_SYNTAX` is used. + * + * @see SyntaxOpts + * @see Token + * + * @param src + * @param opts + */ +export function* tokenize(src: Iterable, opts?: Partial) { + const { scopes: rawScopes, whiteSpace, string } = { + ...DEFAULT_SYNTAX, + ...opts + }; + const scopes = rawScopes + .reduce((acc, x) => acc.concat(x), []) + .join(""); + let token = ""; + let isString = false; + let tokenLine = 0; + let tokenCol = 0; + let line = 0; + let col = -1; + const $ = (value: string): Token => ({ + value, + line: tokenLine, + col: tokenCol + }); + for (let c of src) { + if (c === "\n") { + line++; + col = -1; + } else { + col++; + } + if (!isString) { + if (whiteSpace.test(c)) { + token && (yield $(token)); + token = ""; + } else if (scopes.indexOf(c) !== -1) { + token && (yield $(token)); + tokenLine = line; + tokenCol = col; + yield $(c); + token = ""; + tokenCol++; + } else if (c === string) { + token && (yield $(token)); + tokenLine = line; + tokenCol = col; + token = '"'; + isString = true; + } else { + if (!token) { + tokenLine = line; + tokenCol = col; + } + token += c; + } + } else if (c === string && token[token.length - 1] !== "\\") { + token += '"'; + yield $(token); + token = ""; + isString = false; + } else { + token += c; + } + } +} diff --git a/packages/sexpr/test/index.ts b/packages/sexpr/test/index.ts new file mode 100644 index 0000000000..69664c378f --- /dev/null +++ b/packages/sexpr/test/index.ts @@ -0,0 +1,145 @@ +import { Fn2 } from "@thi.ng/api"; +import { DEFAULT, defmulti } from "@thi.ng/defmulti"; +import * as assert from "assert"; +import { + ASTNode, + Implementations, + parse, + runtime, + Sym, + SyntaxOpts, + tokenize +} from "../src/index"; + +const ops = defmulti((x) => (x).value); +const rt = runtime, any, any>({ + expr: (x, env) => ops(x.children[0], x.children, env), + sym: (x, env) => env[x.value], + str: (x) => x.value, + num: (x) => x.value +}); + +const $eval = (src: string, env: any = {}) => rt(parse(src).children[0], env); + +const op = (fn: Fn2) => ( + _: ASTNode, + vals: ASTNode[], + env: any +) => vals.slice(2).reduce((acc, x) => fn(acc, rt(x, env)), rt(vals[1], env)); + +ops.addAll({ + "+": op((acc, x) => acc + x), + "*": op((acc, x) => acc * x), + "-": op((acc, x) => acc - x), + "/": op((acc, x) => acc / x), + count: (_, [__, x]) => rt(x).length +}); + +ops.add(DEFAULT, (x, [_, ...args], env) => { + const f = env[(x).value]; + assert(!!f, "missing impl"); + return f.apply(null, args.map((a) => rt(a, env))); +}); + +describe("sexpr", () => { + it("basic", () => { + assert.deepEqual(parse(tokenize(`(+ 1 (len "234"))`)), { + type: "root", + children: [ + { + type: "expr", + value: "(", + children: [ + { type: "sym", value: "+" }, + { type: "num", value: 1 }, + { + type: "expr", + value: "(", + children: [ + { type: "sym", value: "len" }, + { type: "str", value: "234" } + ] + } + ] + } + ] + }); + }); + + it("custom syntax", () => { + const syntax: Partial = { + scopes: [["<", ">"], ["{", "}"]], + string: "'" + }; + assert.deepEqual(parse(``, syntax), { + type: "root", + children: [ + { + type: "expr", + value: "<", + children: [ + { + type: "sym", + value: "nest" + }, + { + type: "expr", + value: "{", + children: [ + { + type: "sym", + value: "a" + }, + { + type: "str", + value: "2" + }, + { + type: "sym", + value: "b" + }, + { + type: "num", + value: 3 + } + ] + } + ] + } + ] + }); + }); + + it("unmatched", () => { + assert.throws(() => parse(`(`)); + assert.throws(() => parse(`((`)); + assert.throws(() => parse(`(()`)); + }); + + it("math", () => { + assert.equal( + $eval( + `(/ + (- + (* (count "abc") (+ 100 (* 3 4 5))) + foo) + 100)`, + { foo: -20 } + ), + (3 * (100 + 3 * 4 * 5) - -20) / 100 + ); + }); + + it("fn in env", () => { + assert.equal( + $eval(`(join (+ 1 2) (+ 3 4))`, { + join: (...xs: any[]) => xs.join(",") + }), + "3,7" + ); + }); + + it("missing fn in env", () => { + assert.throws(() => $eval("(foo)")); + }); +}); diff --git a/packages/sexpr/test/tsconfig.json b/packages/sexpr/test/tsconfig.json new file mode 100644 index 0000000000..f6e63560dd --- /dev/null +++ b/packages/sexpr/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build", + "module": "commonjs" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/sexpr/tsconfig.json b/packages/sexpr/tsconfig.json new file mode 100644 index 0000000000..893b9979c5 --- /dev/null +++ b/packages/sexpr/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "module": "es6", + "target": "es6" + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/shader-ast-glsl/CHANGELOG.md b/packages/shader-ast-glsl/CHANGELOG.md index 97012df1cd..d67d8bbee9 100644 --- a/packages/shader-ast-glsl/CHANGELOG.md +++ b/packages/shader-ast-glsl/CHANGELOG.md @@ -3,6 +3,46 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-glsl@0.1.7...@thi.ng/shader-ast-glsl@0.1.8) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/shader-ast-glsl + + + + + +## [0.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-glsl@0.1.6...@thi.ng/shader-ast-glsl@0.1.7) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/shader-ast-glsl + + + + + +## [0.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-glsl@0.1.5...@thi.ng/shader-ast-glsl@0.1.6) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/shader-ast-glsl + + + + + +## [0.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-glsl@0.1.4...@thi.ng/shader-ast-glsl@0.1.5) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/shader-ast-glsl + + + + + +## [0.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-glsl@0.1.3...@thi.ng/shader-ast-glsl@0.1.4) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/shader-ast-glsl + + + + + ## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-glsl@0.1.2...@thi.ng/shader-ast-glsl@0.1.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/shader-ast-glsl diff --git a/packages/shader-ast-glsl/package.json b/packages/shader-ast-glsl/package.json index 54ba9d4966..1ba6338085 100644 --- a/packages/shader-ast-glsl/package.json +++ b/packages/shader-ast-glsl/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/shader-ast-glsl", - "version": "0.1.3", + "version": "0.1.8", "description": "Customizable GLSL code generator for @thi.ng/shader-ast", "module": "./index.js", "main": "./lib/index.js", @@ -29,14 +29,14 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/shader-ast": "^0.2.1" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/shader-ast": "^0.3.2" }, "keywords": [ "AST", diff --git a/packages/shader-ast-glsl/src/api.ts b/packages/shader-ast-glsl/src/api.ts new file mode 100644 index 0000000000..128318abe3 --- /dev/null +++ b/packages/shader-ast-glsl/src/api.ts @@ -0,0 +1,30 @@ +import { Fn } from "@thi.ng/api"; +import { + FloatSym, + Sym, + Term, + Vec2Sym, + Vec4Sym +} from "@thi.ng/shader-ast"; + +export const enum GLSLVersion { + GLES_100 = "100", + GLES_300 = "300 es" +} + +export interface GLSLOpts { + type: "vs" | "fs"; + version: GLSLVersion; + versionPragma: boolean; + prelude: string; +} + +export interface GLSLTarget extends Fn, string> { + gl_FragColor: Vec4Sym; + gl_FragCoord: Vec4Sym; + gl_FragData: Sym<"vec4[]">; + gl_FrontFacing: Sym<"bool">; + gl_PointCoord: Vec2Sym; + gl_PointSize: FloatSym; + gl_Position: Vec4Sym; +} diff --git a/packages/shader-ast-glsl/src/index.ts b/packages/shader-ast-glsl/src/index.ts index 47f352eb09..2cf8efe442 100644 --- a/packages/shader-ast-glsl/src/index.ts +++ b/packages/shader-ast-glsl/src/index.ts @@ -1,229 +1,2 @@ -import { Fn } from "@thi.ng/api"; -import { isBoolean, isNumber } from "@thi.ng/checks"; -import { unsupported } from "@thi.ng/errors"; -import { - defTarget, - FloatSym, - FnCall, - FuncArg, - isMat, - isVec, - itemType, - Sym, - sym, - Term, - Type, - Vec2Sym, - Vec4Sym -} from "@thi.ng/shader-ast"; - -export const enum GLSLVersion { - GLES_100 = "100", - GLES_300 = "300 es" -} - -export interface GLSLOpts { - type: "vs" | "fs"; - version: GLSLVersion; - versionPragma: boolean; - prelude: string; -} - -export interface GLSLTarget extends Fn, string> { - gl_FragColor: Vec4Sym; - gl_FragCoord: Vec4Sym; - gl_FragData: Sym<"vec4[]">; - gl_FrontFacing: Sym<"bool">; - gl_PointCoord: Vec2Sym; - gl_PointSize: FloatSym; - gl_Position: Vec4Sym; -} - -const RE_SEMI = /[};]$/; - -/** - * GLSL code gen, targets GLSL ES 3.00 (WebGL2) by default. - * - * Use options object to configure shader type and GLSL version: `100` - * for WebGL, 300 for WebGL2. Currently, the only differences in terms - * of code generation, not correctness, are: - * - * - attribute, varying, uniform declarations - * - texture lookup function naming - * - * Unsupported features in GLSL 100: - * - * - Fragment shader output vars - * - * @param opts - */ -export const targetGLSL = (opts?: Partial) => { - const _opts: GLSLOpts = { - type: "fs", - version: GLSLVersion.GLES_300, - versionPragma: true, - prelude: "", - ...opts - }; - const isVS = _opts.type === "vs"; - - // TODO update once we have struct support - const $type = (t: Type) => t; - - const $list = (body: Term[], sep = ", ") => body.map(emit).join(sep); - - const $fn = (t: FnCall) => `${t.id}(${$list(t.args)})`; - - const $decl = (sym: Sym | FuncArg, arg = false) => { - const { id, type, opts, init } = >sym; - const res: string[] = []; - if (opts.type) { - let type: string; - if (_opts.version < GLSLVersion.GLES_300) { - if (isVS) { - type = ({ - in: "attribute", - out: "varying", - uni: "uniform" - })[opts.type]; - } else { - type = ({ - in: "varying", - out: null, - uni: "uniform" - })[opts.type]; - !type && - unsupported( - "GLSL 100 doesn't support fragment shader output variables" - ); - } - } else { - opts.loc != null && res.push(`layout(location=${opts.loc}) `); - type = opts.type === "uni" ? "uniform" : opts.type; - } - res.push(type + " "); - } else { - opts.const && res.push("const "); - arg && opts.q && res.push(opts.q + " "); - } - opts.prec && res.push(opts.prec + " "); - res.push($type(itemType(type)), " ", id); - opts.num && res.push(`[${opts.num}]`); - init && res.push(" = ", emit(init)); - return res.join(""); - }; - - const emit: Fn, string> = defTarget({ - arg: (t) => $decl(t, true), - - array_init: (t) => - _opts.version >= GLSLVersion.GLES_300 - ? `${t.type}(${$list(t.init)})` - : unsupported( - `array initializers not available in GLSL ${ - _opts.version - }` - ), - - assign: (t) => emit(t.l) + " = " + emit(t.r), - - ctrl: (t) => t.id, - - call: $fn, - - call_i: (t) => - t.id === "texture" && _opts.version < GLSLVersion.GLES_300 - ? `${t.id}${(>t.args[0]).type.substr(7)}(${$list( - t.args - )})` - : $fn(t), - - decl: (t) => $decl(t.id), - - fn: (t) => - `${$type(t.type)} ${t.id}(${$list(t.args)}) ${emit(t.scope)}`, - - for: (t) => - `for(${t.init ? emit(t.init) : ""}; ${emit(t.test)}; ${ - t.iter ? emit(t.iter) : "" - }) ${emit(t.scope)}`, - - idx: (t) => `${emit(t.val)}[${emit(t.id)}]`, - - if: (t) => { - const res = `if (${emit(t.test)}) ` + emit(t.t); - return t.f ? res + " else " + emit(t.f) : res; - }, - - lit: (t) => { - const v = t.val; - switch (t.type) { - case "bool": - return isBoolean(v) ? String(v) : `bool(${emit(v)})`; - case "float": - return isNumber(v) - ? v === Math.trunc(v) - ? v + ".0" - : String(v) - : `float(${emit(v)})`; - case "int": - case "uint": - return isNumber(v) ? String(v) : `${t.type}(${emit(v)})`; - default: { - if (isVec(t) || isMat(t)) { - return `${t.type}(${$list(v)})`; - } - return unsupported(`unknown type: ${t.type}`); - } - } - }, - - op1: (t) => - t.post ? `(${emit(t.val)}${t.op})` : `(${t.op}${emit(t.val)})`, - - op2: (t) => `(${emit(t.l)} ${t.op} ${emit(t.r)})`, - - ret: (t) => "return" + (t.val ? " " + emit(t.val) : ""), - - scope: (t) => { - let res = t.body - .map(emit) - .reduce( - (acc, x) => (acc.push(RE_SEMI.test(x) ? x : x + ";"), acc), - [] - ) - .join("\n"); - res += t.body.length && !RE_SEMI.test(res) ? ";" : ""; - if (!t.global) { - return `{\n${res}\n}`; - } - if (_opts.prelude) { - res = _opts.prelude + "\n" + res; - } - if (_opts.versionPragma) { - res = `#version ${_opts.version}\n` + res; - } - return res; - }, - - swizzle: (t) => `${emit(t.val)}.${t.id}`, - - sym: (t) => t.id, - - ternary: (t) => `(${emit(t.test)} ? ${emit(t.t)} : ${emit(t.f)})`, - - while: (t) => `while (${emit(t.test)}) ${emit(t.scope)}` - }); - - Object.assign(emit, { - gl_FragColor: sym("vec4", "gl_FragColor"), - gl_FragCoord: sym("vec4", "gl_FragCoord", { const: true }), - gl_FragData: sym("vec4[]", "gl_FragData", { num: 1 }), - gl_FrontFacing: sym("bool", "gl_FrontFacing", { const: true }), - gl_PointCoord: sym("vec2", "gl_PointCoord", { const: true }), - gl_PointSize: sym("float", "gl_PointSize"), - gl_Position: sym("vec4", "gl_Position") - }); - - return emit; -}; +export * from "./api"; +export * from "./target"; diff --git a/packages/shader-ast-glsl/src/target.ts b/packages/shader-ast-glsl/src/target.ts new file mode 100644 index 0000000000..00c93fb8d8 --- /dev/null +++ b/packages/shader-ast-glsl/src/target.ts @@ -0,0 +1,205 @@ +import { Fn } from "@thi.ng/api"; +import { isBoolean, isNumber } from "@thi.ng/checks"; +import { unsupported } from "@thi.ng/errors"; +import { + defTarget, + FnCall, + FuncArg, + isMat, + isVec, + itemType, + Sym, + sym, + Term, + Type +} from "@thi.ng/shader-ast"; +import { GLSLOpts, GLSLTarget, GLSLVersion } from "./api"; + +const RE_SEMI = /[};]$/; + +/** + * GLSL code gen, targets GLSL ES 3.00 (WebGL2) by default. + * + * Use options object to configure shader type and GLSL version: `100` + * for WebGL, 300 for WebGL2. Currently, the only differences in terms + * of code generation, not correctness, are: + * + * - attribute, varying, uniform declarations + * - texture lookup function naming + * + * Unsupported features in GLSL 100: + * + * - Fragment shader output vars + * + * @param opts + */ +export const targetGLSL = (opts?: Partial) => { + const _opts: GLSLOpts = { + type: "fs", + version: GLSLVersion.GLES_300, + versionPragma: true, + prelude: "", + ...opts + }; + const isVS = _opts.type === "vs"; + + // TODO update once we have struct support + const $type = (t: Type) => t; + + const $list = (body: Term[], sep = ", ") => body.map(emit).join(sep); + + const $fn = (t: FnCall) => `${t.id}(${$list(t.args)})`; + + const $decl = (sym: Sym | FuncArg, arg = false) => { + const { id, type, opts, init } = >sym; + const res: string[] = []; + if (opts.type) { + let type: string; + if (_opts.version < GLSLVersion.GLES_300) { + if (isVS) { + type = ({ + in: "attribute", + out: "varying", + uni: "uniform" + })[opts.type]; + } else { + type = ({ + in: "varying", + out: null, + uni: "uniform" + })[opts.type]; + !type && + unsupported( + "GLSL 100 doesn't support fragment shader output variables" + ); + } + } else { + opts.loc != null && res.push(`layout(location=${opts.loc}) `); + type = opts.type === "uni" ? "uniform" : opts.type; + } + res.push(type + " "); + } else { + opts.const && res.push("const "); + arg && opts.q && res.push(opts.q + " "); + } + opts.prec && res.push(opts.prec + " "); + res.push($type(itemType(type)), " ", id); + opts.num && res.push(`[${opts.num}]`); + init && res.push(" = ", emit(init)); + return res.join(""); + }; + + const emit: Fn, string> = defTarget({ + arg: (t) => $decl(t, true), + + array_init: (t) => + _opts.version >= GLSLVersion.GLES_300 + ? `${t.type}(${$list(t.init)})` + : unsupported( + `array initializers not available in GLSL ${ + _opts.version + }` + ), + + assign: (t) => emit(t.l) + " = " + emit(t.r), + + ctrl: (t) => t.id, + + call: $fn, + + call_i: (t) => + t.id === "texture" && _opts.version < GLSLVersion.GLES_300 + ? `${t.id}${(>t.args[0]).type.substr(7)}(${$list( + t.args + )})` + : $fn(t), + + decl: (t) => $decl(t.id), + + fn: (t) => + `${$type(t.type)} ${t.id}(${$list(t.args)}) ${emit(t.scope)}`, + + for: (t) => + `for(${t.init ? emit(t.init) : ""}; ${emit(t.test)}; ${ + t.iter ? emit(t.iter) : "" + }) ${emit(t.scope)}`, + + idx: (t) => `${emit(t.val)}[${emit(t.id)}]`, + + if: (t) => { + const res = `if (${emit(t.test)}) ` + emit(t.t); + return t.f ? res + " else " + emit(t.f) : res; + }, + + lit: (t) => { + const v = t.val; + switch (t.type) { + case "bool": + return isBoolean(v) ? String(v) : `bool(${emit(v)})`; + case "float": + return isNumber(v) + ? v === Math.trunc(v) + ? v + ".0" + : String(v) + : `float(${emit(v)})`; + case "int": + case "uint": + return isNumber(v) ? String(v) : `${t.type}(${emit(v)})`; + default: { + if (isVec(t) || isMat(t)) { + return `${t.type}(${$list(v)})`; + } + return unsupported(`unknown type: ${t.type}`); + } + } + }, + + op1: (t) => + t.post ? `(${emit(t.val)}${t.op})` : `(${t.op}${emit(t.val)})`, + + op2: (t) => `(${emit(t.l)} ${t.op} ${emit(t.r)})`, + + ret: (t) => "return" + (t.val ? " " + emit(t.val) : ""), + + scope: (t) => { + let res = t.body + .map(emit) + .reduce( + (acc, x) => (acc.push(RE_SEMI.test(x) ? x : x + ";"), acc), + [] + ) + .join("\n"); + res += t.body.length && !RE_SEMI.test(res) ? ";" : ""; + if (!t.global) { + return `{\n${res}\n}`; + } + if (_opts.prelude) { + res = _opts.prelude + "\n" + res; + } + if (_opts.versionPragma) { + res = `#version ${_opts.version}\n` + res; + } + return res; + }, + + swizzle: (t) => `${emit(t.val)}.${t.id}`, + + sym: (t) => t.id, + + ternary: (t) => `(${emit(t.test)} ? ${emit(t.t)} : ${emit(t.f)})`, + + while: (t) => `while (${emit(t.test)}) ${emit(t.scope)}` + }); + + Object.assign(emit, { + gl_FragColor: sym("vec4", "gl_FragColor"), + gl_FragCoord: sym("vec4", "gl_FragCoord", { const: true }), + gl_FragData: sym("vec4[]", "gl_FragData", { num: 1 }), + gl_FrontFacing: sym("bool", "gl_FrontFacing", { const: true }), + gl_PointCoord: sym("vec2", "gl_PointCoord", { const: true }), + gl_PointSize: sym("float", "gl_PointSize"), + gl_Position: sym("vec4", "gl_Position") + }); + + return emit; +}; diff --git a/packages/shader-ast-js/CHANGELOG.md b/packages/shader-ast-js/CHANGELOG.md index 6c1dbd00d3..95b18c39fc 100644 --- a/packages/shader-ast-js/CHANGELOG.md +++ b/packages/shader-ast-js/CHANGELOG.md @@ -3,6 +3,66 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.4.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-js@0.4.0...@thi.ng/shader-ast-js@0.4.1) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/shader-ast-js + + + + + +# [0.4.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-js@0.3.1...@thi.ng/shader-ast-js@0.4.0) (2019-09-21) + + +### Bug Fixes + +* **shader-ast-js:** fix divisions ([bc81ff8](https://github.com/thi-ng/umbrella/commit/bc81ff8)) + + +### Features + +* **shader-ast-js:** add div-by-zero guards ([233528d](https://github.com/thi-ng/umbrella/commit/233528d)) +* **shader-ast-js:** replace JS runtime fns, add doc strings ([0798a35](https://github.com/thi-ng/umbrella/commit/0798a35)) + + + + + +## [0.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-js@0.3.0...@thi.ng/shader-ast-js@0.3.1) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/shader-ast-js + + + + + +# [0.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-js@0.2.3...@thi.ng/shader-ast-js@0.3.0) (2019-08-17) + + +### Features + +* **shader-ast-js:** add support for 2-arg atan(), move types to api.ts ([a760085](https://github.com/thi-ng/umbrella/commit/a760085)) + + + + + +## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-js@0.2.2...@thi.ng/shader-ast-js@0.2.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/shader-ast-js + + + + + +## [0.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-js@0.2.1...@thi.ng/shader-ast-js@0.2.2) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/shader-ast-js + + + + + ## [0.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-js@0.2.0...@thi.ng/shader-ast-js@0.2.1) (2019-07-31) **Note:** Version bump only for package @thi.ng/shader-ast-js diff --git a/packages/shader-ast-js/package.json b/packages/shader-ast-js/package.json index 087b48153e..b6187c9cbe 100644 --- a/packages/shader-ast-js/package.json +++ b/packages/shader-ast-js/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/shader-ast-js", - "version": "0.2.1", + "version": "0.4.1", "description": "Customizable JS code generator, compiler & runtime for @thi.ng/shader-ast", "module": "./index.js", "main": "./lib/index.js", @@ -29,17 +29,18 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/math": "^1.4.2", - "@thi.ng/matrices": "^0.5.3", - "@thi.ng/shader-ast": "^0.2.1", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/math": "^1.5.0", + "@thi.ng/matrices": "^0.5.9", + "@thi.ng/pixel": "^0.1.5", + "@thi.ng/shader-ast": "^0.3.2", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "AST", diff --git a/packages/shader-ast-js/src/api.ts b/packages/shader-ast-js/src/api.ts new file mode 100644 index 0000000000..ca9dd0d8e0 --- /dev/null +++ b/packages/shader-ast-js/src/api.ts @@ -0,0 +1,212 @@ +import { + Fn, + Fn2, + Fn3, + Fn4, + Fn5, + Fn6 +} from "@thi.ng/api"; +import { Mat } from "@thi.ng/matrices"; +import { Term } from "@thi.ng/shader-ast"; +import { Vec } from "@thi.ng/vectors"; + +export interface JSTarget extends Fn, string> { + /** + * Compiles given AST to JavaScript, using optional `env` as backend + * for various operators / builtins. If `env` is not given the + * bundled `JS_DEFAULT_ENV` is used (based on thi.ng/vectors and + * thi.ng/matrices packages). + * + * Any functions defined in the given AST will be exported using + * their defined name via the returned object. + * + * ``` + * const js = targetJS(); + * const module = js.compile( + * defn("float", "foo", [["float"]], (x)=> [ret(mul(x, float(10)))]) + * ); + * + * module.foo(42) + * // 420 + * + * module.foo.toString() + * // function foo(_sym0) { + * // return (_sym0 * 10); + * // } + * ``` + * + * @param tree + * @param env + */ + compile(tree: Term, env?: JSEnv): any; +} + +export interface JSBuiltinsCommon { + abs: Fn; + clamp: Fn3; + max: Fn2; + min: Fn2; + sign: Fn; +} + +export interface JSBuiltinsMath { + sub1: Fn; + add: Fn2; + sub: Fn2; + mul: Fn2; + div: Fn2; + inc: Fn; + dec: Fn; +} + +export interface JSBuiltinsBinary { + bitand: Fn2; + lshift: Fn2; + bitnot1: Fn2; + bitor: Fn2; + rshift: Fn2; + bitxor: Fn2; +} + +export interface JSBuiltinsFloat extends JSBuiltinsCommon { + acos: Fn; + asin: Fn; + atan: Fn; + atannn: Fn2; + ceil: Fn; + cos: Fn; + degrees: Fn; + dFdx: Fn; + dFdy: Fn; + exp: Fn; + exp2: Fn; + floor: Fn; + fract: Fn; + fwidth: Fn; + inversesqrt: Fn; + log: Fn; + log2: Fn; + mix: Fn3; + mixn: Fn3; + mod: Fn2; + modn: Fn2; + pow: Fn2; + radians: Fn; + sin: Fn; + smoothstep: Fn3; + sqrt: Fn; + step: Fn2; + tan: Fn; +} + +export interface JSBuiltinsInt + extends JSBuiltinsCommon, + JSBuiltinsMath, + JSBuiltinsBinary { + modi: Fn2; +} + +export interface JSBuiltinsVecScalar { + addvn: Fn2; + subvn: Fn2; + mulvn: Fn2; + divvn: Fn2; + addnv: Fn2; + subnv: Fn2; + mulnv: Fn2; + divnv: Fn2; +} + +export interface JSBuiltinsVec + extends JSBuiltinsFloat, + JSBuiltinsMath, + JSBuiltinsVecScalar { + distance: Fn2; + dot: Fn2; + faceForward: Fn3; + length: Fn; + normalize: Fn; + reflect: Fn2; + refract: Fn3; +} + +export interface JSBuiltinsVec3 extends JSBuiltinsVec { + cross: Fn2; +} + +export interface JSBuiltinsIntVec + extends JSBuiltinsInt, + JSBuiltinsVecScalar, + JSBuiltinsBinary { + modivn: Fn2; + modinv: Fn2; +} + +export interface JSBuiltinsMat + extends JSBuiltinsMath, + JSBuiltinsVecScalar { + mulm: Fn2; + mulvm: Fn2; + mulmv: Fn2; +} + +export interface JSBuiltinsSampler { + texelFetch: Fn3; + texelFetchOffset: Fn4; + texture: Fn3; + texturen: Fn3; + textureGrad: Fn4; + textureGradn: Fn4; + textureLod: Fn3; + textureLodn: Fn3; + textureOffset: Fn4; + textureOffsetn: Fn4; + textureProj: Fn3; + textureProjn: Fn3; + textureSize: Fn2; +} + +export interface JSEnv { + vec2n: Fn; + vec3n: Fn; + vec3vn: Fn2; + vec4n: Fn; + vec4vn: Fn2; + vec4vnn: Fn3; + vec4vv: Fn2; + mat2n: Fn; + mat2vv: Fn2; + mat3n: Fn; + mat3vvv: Fn3; + mat4n: Fn; + mat4vvvv: Fn4; + // swizzle1: Fn2; + swizzle2: Fn3; + swizzle3: Fn4; + swizzle4: Fn5; + // set_swizzle1: Fn3; + set_swizzle2: Fn4; + set_swizzle3: Fn5; + set_swizzle4: Fn6; + float: JSBuiltinsFloat; + int: JSBuiltinsInt; + uint: JSBuiltinsInt; + vec2: JSBuiltinsVec; + vec3: JSBuiltinsVec3; + vec4: JSBuiltinsVec; + ivec2: JSBuiltinsIntVec; + ivec3: JSBuiltinsIntVec; + ivec4: JSBuiltinsIntVec; + uvec2: JSBuiltinsIntVec; + uvec3: JSBuiltinsIntVec; + uvec4: JSBuiltinsIntVec; + mat2: JSBuiltinsMat; + mat3: JSBuiltinsMat; + mat4: JSBuiltinsMat; + sampler1D: JSBuiltinsSampler; + sampler2D: JSBuiltinsSampler; + sampler3D: JSBuiltinsSampler; + samplerCube: JSBuiltinsSampler; + sampler2DShadow: JSBuiltinsSampler; + samplerCubeShadow: JSBuiltinsSampler; +} diff --git a/packages/shader-ast-js/src/env.ts b/packages/shader-ast-js/src/env.ts new file mode 100644 index 0000000000..26658f1985 --- /dev/null +++ b/packages/shader-ast-js/src/env.ts @@ -0,0 +1,98 @@ +import { + mat22n, + mat22v, + mat33n, + mat33v, + mat44n, + mat44v +} from "@thi.ng/matrices"; +import { + setSwizzle2, + setSwizzle3, + setSwizzle4, + setVN3, + setVN4, + setVV4, + swizzle2, + swizzle3, + swizzle4, + ZERO3, + ZERO4 +} from "@thi.ng/vectors"; +import { JSBuiltinsSampler, JSEnv } from "./api"; +import { FLOAT } from "./env/float"; +import { INT } from "./env/int"; +import { IVEC2 } from "./env/ivec2"; +import { IVEC3 } from "./env/ivec3"; +import { IVEC4 } from "./env/ivec4"; +import { MAT2 } from "./env/mat2"; +import { MAT3 } from "./env/mat3"; +import { MAT4 } from "./env/mat4"; +import { UINT } from "./env/uint"; +import { UVEC2 } from "./env/uvec2"; +import { UVEC3 } from "./env/uvec3"; +import { UVEC4 } from "./env/uvec4"; +import { VEC2 } from "./env/vec2"; +import { VEC3 } from "./env/vec3"; +import { VEC4 } from "./env/vec4"; + +// TODO texture lookups +// all texture fns currently return [0,0,0,0] or 0 +const SAMPLER_TODO: JSBuiltinsSampler = { + texelFetch: () => ZERO4, + texelFetchOffset: () => ZERO4, + texture: () => ZERO4, + texturen: () => 0, + textureGrad: () => ZERO4, + textureGradn: () => 0, + textureLod: () => ZERO4, + textureLodn: () => 0, + textureOffset: () => ZERO4, + textureOffsetn: () => 0, + textureProj: () => ZERO4, + textureProjn: () => 0, + textureSize: () => ZERO3 +}; + +export const JS_DEFAULT_ENV: JSEnv = { + vec2n: (n) => [n, n], + vec3n: (n) => [n, n, n], + vec4n: (n) => [n, n, n, n], + vec3vn: (a, n) => setVN3([], a, n), + vec4vn: (a, n) => setVN4([], a, n), + vec4vnn: (a, z, w) => setVV4([], a, [z, w]), + vec4vv: (a, b) => setVV4([], a, b), + mat2n: (n) => mat22n([], n), + mat2vv: (a, b) => mat22v([], a, b), + mat3n: (n) => mat33n([], n), + mat3vvv: (a, b, c) => mat33v([], a, b, c), + mat4n: (n) => mat44n([], n), + mat4vvvv: (a, b, c, d) => mat44v([], a, b, c, d), + swizzle2: (a, b, c) => swizzle2([], a, b, c), + swizzle3: (a, b, c, d) => swizzle3([], a, b, c, d), + swizzle4: (a, b, c, d, e) => swizzle4([], a, b, c, d, e), + set_swizzle2: setSwizzle2, + set_swizzle3: setSwizzle3, + set_swizzle4: setSwizzle4, + float: FLOAT, + int: INT, + uint: UINT, + vec2: VEC2, + vec3: VEC3, + vec4: VEC4, + ivec2: IVEC2, + ivec3: IVEC3, + ivec4: IVEC4, + uvec2: UVEC2, + uvec3: UVEC3, + uvec4: UVEC4, + mat2: MAT2, + mat3: MAT3, + mat4: MAT4, + sampler1D: SAMPLER_TODO, + sampler2D: SAMPLER_TODO, + sampler3D: SAMPLER_TODO, + samplerCube: SAMPLER_TODO, + sampler2DShadow: SAMPLER_TODO, + samplerCubeShadow: SAMPLER_TODO +}; diff --git a/packages/shader-ast-js/src/env/float.ts b/packages/shader-ast-js/src/env/float.ts new file mode 100644 index 0000000000..76874a76f7 --- /dev/null +++ b/packages/shader-ast-js/src/env/float.ts @@ -0,0 +1,47 @@ +import { + clamp, + deg, + fmod, + fract, + mix, + rad, + smoothStep, + step +} from "@thi.ng/math"; +import { JSBuiltinsFloat } from "../api"; + +export const FLOAT: JSBuiltinsFloat = { + abs: Math.abs, + acos: Math.acos, + asin: Math.asin, + atan: Math.atan, + atannn: Math.atan2, + ceil: Math.ceil, + clamp, + cos: Math.cos, + degrees: deg, + dFdx: () => 0, + dFdy: () => 0, + exp: Math.exp, + exp2: (x) => Math.pow(2, x), + floor: Math.floor, + fract, + fwidth: () => 0, + inversesqrt: (x) => 1 / Math.sqrt(x), + log: Math.log, + log2: Math.log2, + max: Math.max, + min: Math.min, + mix, + mixn: mix, + mod: fmod, + modn: fmod, + pow: Math.pow, + radians: rad, + sign: Math.sign, + sin: Math.sin, + smoothstep: smoothStep, + sqrt: Math.sqrt, + step, + tan: Math.tan +}; diff --git a/packages/shader-ast-js/src/env/int.ts b/packages/shader-ast-js/src/env/int.ts new file mode 100644 index 0000000000..301c26e3d5 --- /dev/null +++ b/packages/shader-ast-js/src/env/int.ts @@ -0,0 +1,24 @@ +import { clamp } from "@thi.ng/math"; +import { JSBuiltinsInt } from "../api"; + +export const INT: JSBuiltinsInt = { + abs: Math.abs, + add: (a, b) => (a + b) | 0, + bitand: (a, b) => a & b, + bitnot1: (a) => ~a, + bitor: (a, b) => a | b, + bitxor: (a, b) => a ^ b, + clamp, + dec: (a) => (a - 1) | 0, + div: (a, b) => (a / b) | 0, + inc: (a) => (a + 1) | 0, + lshift: (a, b) => a << b, + max: Math.max, + min: Math.min, + modi: (a, b) => a % b, + mul: (a, b) => (a * b) | 0, + rshift: (a, b) => a >> b, + sign: Math.sign, + sub: (a, b) => (a - b) | 0, + sub1: (a) => -a | 0 +}; diff --git a/packages/shader-ast-js/src/env/ivec2.ts b/packages/shader-ast-js/src/env/ivec2.ts new file mode 100644 index 0000000000..84ef90beda --- /dev/null +++ b/packages/shader-ast-js/src/env/ivec2.ts @@ -0,0 +1,45 @@ +import { + addI2, + addNI2, + bitAndI2, + bitNotI2, + bitOrI2, + bitXorI2, + divI2, + divNI2, + lshiftI2, + mod2, + modN2, + mulI2, + mulNI2, + rshiftI2, + subI2, + subNI2 +} from "@thi.ng/vectors"; +import { JSBuiltinsIntVec } from "../api"; +import { VEC2 } from "./vec2"; + +export const IVEC2: JSBuiltinsIntVec = { + ...VEC2, + add: (a, b) => addI2([], a, b), + addvn: (a, b) => addNI2([], a, b), + addnv: (a, b) => addNI2([], b, a), + div: (a, b) => divI2([], a, b), + divvn: (a, b) => divNI2([], a, b), + divnv: (a, b) => divI2(null, [a, a], b), + modi: (a, b) => mod2([], a, b), + modivn: (a, b) => modN2([], a, b), + modinv: (a, b) => mod2(null, [a, a], b), + mul: (a, b) => mulI2([], a, b), + mulvn: (a, b) => mulNI2([], a, b), + mulnv: (a, b) => mulNI2([], b, a), + sub: (a, b) => subI2([], a, b), + subvn: (a, b) => subNI2([], a, b), + subnv: (a, b) => subI2(null, [a, a], b), + bitand: (a, b) => bitAndI2([], a, b), + lshift: (a, b) => lshiftI2([], a, b), + bitnot1: (a) => bitNotI2([], a), + bitor: (a, b) => bitOrI2([], a, b), + rshift: (a, b) => rshiftI2([], a, b), + bitxor: (a, b) => bitXorI2([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/ivec3.ts b/packages/shader-ast-js/src/env/ivec3.ts new file mode 100644 index 0000000000..a5496ab76e --- /dev/null +++ b/packages/shader-ast-js/src/env/ivec3.ts @@ -0,0 +1,45 @@ +import { + addI3, + addNI3, + bitAndI3, + bitNotI3, + bitOrI3, + bitXorI3, + divI3, + divNI3, + lshiftI3, + mod3, + modN3, + mulI3, + mulNI3, + rshiftI3, + subI3, + subNI3 +} from "@thi.ng/vectors"; +import { JSBuiltinsIntVec } from "../api"; +import { VEC3 } from "./vec3"; + +export const IVEC3: JSBuiltinsIntVec = { + ...VEC3, + add: (a, b) => addI3([], a, b), + addvn: (a, b) => addNI3([], a, b), + addnv: (a, b) => addNI3([], b, a), + div: (a, b) => divI3([], a, b), + divvn: (a, b) => divNI3([], a, b), + divnv: (a, b) => divI3(null, [a, a, a], b), + modi: (a, b) => mod3([], a, b), + modivn: (a, b) => modN3([], a, b), + modinv: (a, b) => mod3(null, [a, a, a], b), + mul: (a, b) => mulI3([], a, b), + mulvn: (a, b) => mulNI3([], a, b), + mulnv: (a, b) => mulNI3([], b, a), + sub: (a, b) => subI3([], a, b), + subvn: (a, b) => subNI3([], a, b), + subnv: (a, b) => subI3(null, [a, a, a], b), + bitand: (a, b) => bitAndI3([], a, b), + lshift: (a, b) => lshiftI3([], a, b), + bitnot1: (a) => bitNotI3([], a), + bitor: (a, b) => bitOrI3([], a, b), + rshift: (a, b) => rshiftI3([], a, b), + bitxor: (a, b) => bitXorI3([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/ivec4.ts b/packages/shader-ast-js/src/env/ivec4.ts new file mode 100644 index 0000000000..ad0ed5f36a --- /dev/null +++ b/packages/shader-ast-js/src/env/ivec4.ts @@ -0,0 +1,45 @@ +import { + addI4, + addNI4, + bitAndI4, + bitNotI4, + bitOrI4, + bitXorI4, + divI4, + divNI4, + lshiftI4, + mod4, + modN4, + mulI4, + mulNI4, + rshiftI4, + subI4, + subNI4 +} from "@thi.ng/vectors"; +import { JSBuiltinsIntVec } from "../api"; +import { VEC4 } from "./vec4"; + +export const IVEC4: JSBuiltinsIntVec = { + ...VEC4, + add: (a, b) => addI4([], a, b), + addvn: (a, b) => addNI4([], a, b), + addnv: (a, b) => addNI4([], b, a), + div: (a, b) => divI4([], a, b), + divvn: (a, b) => divNI4([], a, b), + divnv: (a, b) => divI4(null, [a, a, a, a], b), + modi: (a, b) => mod4([], a, b), + modivn: (a, b) => modN4([], a, b), + modinv: (a, b) => mod4(null, [a, a, a, a], b), + mul: (a, b) => mulI4([], a, b), + mulvn: (a, b) => mulNI4([], a, b), + mulnv: (a, b) => mulNI4([], b, a), + sub: (a, b) => subI4([], a, b), + subvn: (a, b) => subNI4([], a, b), + subnv: (a, b) => subI4(null, [a, a, a, a], b), + bitand: (a, b) => bitAndI4([], a, b), + lshift: (a, b) => lshiftI4([], a, b), + bitnot1: (a) => bitNotI4([], a), + bitor: (a, b) => bitOrI4([], a, b), + rshift: (a, b) => rshiftI4([], a, b), + bitxor: (a, b) => bitXorI4([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/mat2.ts b/packages/shader-ast-js/src/env/mat2.ts new file mode 100644 index 0000000000..fe8450bbf3 --- /dev/null +++ b/packages/shader-ast-js/src/env/mat2.ts @@ -0,0 +1,36 @@ +import { + add22, + addN22, + div22, + divN22, + mul22, + mulM22, + mulN22, + mulV22, + mulVM22, + sub22, + subN22 +} from "@thi.ng/matrices"; +import { neg } from "@thi.ng/vectors"; +import { JSBuiltinsMat } from "../api"; + +export const MAT2: JSBuiltinsMat = { + add: (a, b) => add22([], a, b), + addnv: (a, b) => addN22([], b, a), + addvn: (a, b) => addN22([], a, b), + dec: (a) => subN22([], a, 1), + div: (a, b) => div22([], a, b), + divnv: (a, b) => div22(null, [a, a, a, a], b), + divvn: (a, b) => divN22([], a, b), + inc: (a) => addN22([], a, 1), + mul: (a, b) => mul22([], a, b), + mulm: (a, b) => mulM22([], a, b), + mulmv: (a, b) => mulV22([], a, b), + mulnv: (a, b) => mulN22([], b, a), + mulvm: (a, b) => mulVM22([], a, b), + mulvn: (a, b) => mulN22([], a, b), + sub: (a, b) => sub22([], a, b), + sub1: (a) => neg([], a), + subnv: (a, b) => sub22(null, [a, a, a, a], b), + subvn: (a, b) => subN22([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/mat3.ts b/packages/shader-ast-js/src/env/mat3.ts new file mode 100644 index 0000000000..69b96ecf85 --- /dev/null +++ b/packages/shader-ast-js/src/env/mat3.ts @@ -0,0 +1,36 @@ +import { + add33, + addN33, + div33, + divN33, + mul33, + mulM33, + mulN33, + mulV33, + mulVM33, + sub33, + subN33 +} from "@thi.ng/matrices"; +import { neg, vecOf } from "@thi.ng/vectors"; +import { JSBuiltinsMat } from "../api"; + +export const MAT3: JSBuiltinsMat = { + add: (a, b) => add33([], a, b), + addnv: (a, b) => addN33([], b, a), + addvn: (a, b) => addN33([], a, b), + dec: (a) => subN33([], a, 1), + div: (a, b) => div33([], a, b), + divnv: (a, b) => div33(null, vecOf(9, a), b), + divvn: (a, b) => divN33([], a, b), + inc: (a) => addN33([], a, 1), + mul: (a, b) => mul33([], a, b), + mulm: (a, b) => mulM33([], a, b), + mulmv: (a, b) => mulV33([], a, b), + mulnv: (a, b) => mulN33([], b, a), + mulvm: (a, b) => mulVM33([], a, b), + mulvn: (a, b) => mulN33([], a, b), + sub: (a, b) => sub33([], a, b), + sub1: (a) => neg([], a), + subnv: (a, b) => sub33(null, vecOf(9, a), b), + subvn: (a, b) => subN33([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/mat4.ts b/packages/shader-ast-js/src/env/mat4.ts new file mode 100644 index 0000000000..30805a2fe4 --- /dev/null +++ b/packages/shader-ast-js/src/env/mat4.ts @@ -0,0 +1,36 @@ +import { + add44, + addN44, + div44, + divN44, + mul44, + mulM44, + mulN44, + mulV44, + mulVM44, + sub44, + subN44 +} from "@thi.ng/matrices"; +import { neg, vecOf } from "@thi.ng/vectors"; +import { JSBuiltinsMat } from "../api"; + +export const MAT4: JSBuiltinsMat = { + add: (a, b) => add44([], a, b), + addnv: (a, b) => addN44([], b, a), + addvn: (a, b) => addN44([], a, b), + dec: (a) => subN44([], a, 1), + div: (a, b) => div44([], a, b), + divnv: (a, b) => div44(null, vecOf(16, a), b), + divvn: (a, b) => divN44([], a, b), + inc: (a) => addN44([], a, 1), + mul: (a, b) => mul44([], a, b), + mulm: (a, b) => mulM44([], a, b), + mulmv: (a, b) => mulV44([], a, b), + mulnv: (a, b) => mulN44([], b, a), + mulvm: (a, b) => mulVM44([], a, b), + mulvn: (a, b) => mulN44([], a, b), + sub: (a, b) => sub44([], a, b), + sub1: (a) => neg([], a), + subnv: (a, b) => sub44(null, vecOf(16, a), b), + subvn: (a, b) => subN44([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/uint.ts b/packages/shader-ast-js/src/env/uint.ts new file mode 100644 index 0000000000..72e1a872c0 --- /dev/null +++ b/packages/shader-ast-js/src/env/uint.ts @@ -0,0 +1,24 @@ +import { clamp } from "@thi.ng/math"; +import { JSBuiltinsInt } from "../api"; + +export const UINT: JSBuiltinsInt = { + abs: Math.abs, + add: (a, b) => (a + b) >>> 0, + bitand: (a, b) => (a & b) >>> 0, + bitnot1: (a) => ~a >>> 0, + bitor: (a, b) => (a | b) >>> 0, + bitxor: (a, b) => (a ^ b) >>> 0, + clamp, + dec: (a) => (a - 1) >>> 0, + div: (a, b) => (a / b) >>> 0, + inc: (a) => (a + 1) >>> 0, + lshift: (a, b) => (a << b) >>> 0, + max: Math.max, + min: Math.min, + modi: (a, b) => a % b, + mul: (a, b) => (a * b) >>> 0, + rshift: (a, b) => a >>> b, + sign: Math.sign, + sub: (a, b) => (a - b) >>> 0, + sub1: (a) => -a >>> 0 +}; diff --git a/packages/shader-ast-js/src/env/uvec2.ts b/packages/shader-ast-js/src/env/uvec2.ts new file mode 100644 index 0000000000..adb7145514 --- /dev/null +++ b/packages/shader-ast-js/src/env/uvec2.ts @@ -0,0 +1,45 @@ +import { + addNU2, + addU2, + bitAndU2, + bitNotU2, + bitOrU2, + bitXorU2, + divNU2, + divU2, + lshiftU2, + mod2, + modN2, + mulNU2, + mulU2, + rshiftU2, + subNU2, + subU2 +} from "@thi.ng/vectors"; +import { JSBuiltinsIntVec } from "../api"; +import { VEC2 } from "./vec2"; + +export const UVEC2: JSBuiltinsIntVec = { + ...VEC2, + add: (a, b) => addU2([], a, b), + addvn: (a, b) => addNU2([], a, b), + addnv: (a, b) => addNU2([], b, a), + div: (a, b) => divU2([], a, b), + divvn: (a, b) => divNU2([], a, b), + divnv: (a, b) => divU2(null, [a, a], b), + modi: (a, b) => mod2([], a, b), + modivn: (a, b) => modN2([], a, b), + modinv: (a, b) => mod2(null, [a, a], b), + mul: (a, b) => mulU2([], a, b), + mulvn: (a, b) => mulNU2([], a, b), + mulnv: (a, b) => mulNU2([], b, a), + sub: (a, b) => subU2([], a, b), + subvn: (a, b) => subNU2([], a, b), + subnv: (a, b) => subU2(null, [a, a], b), + bitand: (a, b) => bitAndU2([], a, b), + lshift: (a, b) => lshiftU2([], a, b), + bitnot1: (a) => bitNotU2([], a), + bitor: (a, b) => bitOrU2([], a, b), + rshift: (a, b) => rshiftU2([], a, b), + bitxor: (a, b) => bitXorU2([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/uvec3.ts b/packages/shader-ast-js/src/env/uvec3.ts new file mode 100644 index 0000000000..6303925fc2 --- /dev/null +++ b/packages/shader-ast-js/src/env/uvec3.ts @@ -0,0 +1,45 @@ +import { + addNU3, + addU3, + bitAndU3, + bitNotU3, + bitOrU3, + bitXorU3, + divNU3, + divU3, + lshiftU3, + mod3, + modN3, + mulNU3, + mulU3, + rshiftU3, + subNU3, + subU3 +} from "@thi.ng/vectors"; +import { JSBuiltinsIntVec } from "../api"; +import { VEC3 } from "./vec3"; + +export const UVEC3: JSBuiltinsIntVec = { + ...VEC3, + add: (a, b) => addU3([], a, b), + addvn: (a, b) => addNU3([], a, b), + addnv: (a, b) => addNU3([], b, a), + div: (a, b) => divU3([], a, b), + divvn: (a, b) => divNU3([], a, b), + divnv: (a, b) => divU3(null, [a, a, a], b), + modi: (a, b) => mod3([], a, b), + modivn: (a, b) => modN3([], a, b), + modinv: (a, b) => mod3(null, [a, a, a], b), + mul: (a, b) => mulU3([], a, b), + mulvn: (a, b) => mulNU3([], a, b), + mulnv: (a, b) => mulNU3([], b, a), + sub: (a, b) => subU3([], a, b), + subvn: (a, b) => subNU3([], a, b), + subnv: (a, b) => subU3(null, [a, a, a], b), + bitand: (a, b) => bitAndU3([], a, b), + lshift: (a, b) => lshiftU3([], a, b), + bitnot1: (a) => bitNotU3([], a), + bitor: (a, b) => bitOrU3([], a, b), + rshift: (a, b) => rshiftU3([], a, b), + bitxor: (a, b) => bitXorU3([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/uvec4.ts b/packages/shader-ast-js/src/env/uvec4.ts new file mode 100644 index 0000000000..94c0c4709a --- /dev/null +++ b/packages/shader-ast-js/src/env/uvec4.ts @@ -0,0 +1,45 @@ +import { + addNU4, + addU4, + bitAndU4, + bitNotU4, + bitOrU4, + bitXorU4, + divNU4, + divU4, + lshiftU4, + mod4, + modN4, + mulNU4, + mulU4, + rshiftU4, + subNU4, + subU4 +} from "@thi.ng/vectors"; +import { JSBuiltinsIntVec } from "../api"; +import { VEC4 } from "./vec4"; + +export const UVEC4: JSBuiltinsIntVec = { + ...VEC4, + add: (a, b) => addU4([], a, b), + addvn: (a, b) => addNU4([], a, b), + addnv: (a, b) => addNU4([], b, a), + div: (a, b) => divU4([], a, b), + divvn: (a, b) => divNU4([], a, b), + divnv: (a, b) => divU4(null, [a, a, a, a], b), + modi: (a, b) => mod4([], a, b), + modivn: (a, b) => modN4([], a, b), + modinv: (a, b) => mod4(null, [a, a, a, a], b), + mul: (a, b) => mulU4([], a, b), + mulvn: (a, b) => mulNU4([], a, b), + mulnv: (a, b) => mulNU4([], b, a), + sub: (a, b) => subU4([], a, b), + subvn: (a, b) => subNU4([], a, b), + subnv: (a, b) => subU4(null, [a, a, a, a], b), + bitand: (a, b) => bitAndU4([], a, b), + lshift: (a, b) => lshiftU4([], a, b), + bitnot1: (a) => bitNotU4([], a), + bitor: (a, b) => bitOrU4([], a, b), + rshift: (a, b) => rshiftU4([], a, b), + bitxor: (a, b) => bitXorU4([], a, b) +}; diff --git a/packages/shader-ast-js/src/env/vec2.ts b/packages/shader-ast-js/src/env/vec2.ts new file mode 100644 index 0000000000..ebea99558e --- /dev/null +++ b/packages/shader-ast-js/src/env/vec2.ts @@ -0,0 +1,108 @@ +import { + abs2, + acos2, + add2, + addN2, + asin2, + atan_22, + atan2, + ceil2, + clamp2, + cos2, + degrees2, + dist, + div2, + divN2, + dot2, + exp_22, + exp2, + faceForward, + floor2, + fmod2, + fmodN2, + fract2, + invSqrt2, + log_22, + log2, + mag, + max2, + min2, + mix2, + mixN2, + mul2, + mulN2, + neg, + normalize, + pow2, + radians2, + reflect, + refract, + sign2, + sin2, + smoothStep2, + sqrt2, + step2, + sub2, + subN2, + tan2, + ZERO2 +} from "@thi.ng/vectors"; +import { JSBuiltinsVec } from "../api"; + +export const VEC2: JSBuiltinsVec = { + abs: (a) => abs2([], a), + acos: (a) => acos2([], a), + add: (a, b) => add2([], a, b), + addnv: (a, b) => addN2([], b, a), + addvn: (a, b) => addN2([], a, b), + asin: (a) => asin2([], a), + atan: (a) => atan2([], a), + atannn: (a, b) => atan_22([], a, b), + ceil: (a) => ceil2([], a), + clamp: (x, a, b) => clamp2([], x, a, b), + cos: (a) => cos2([], a), + dec: (a) => subN2([], a, 1), + degrees: (a) => degrees2([], a), + dFdx: () => ZERO2, + dFdy: () => ZERO2, + distance: dist, + div: (a, b) => div2([], a, b), + divnv: (a, b) => div2(null, [a, a], b), + divvn: (a, b) => divN2([], a, b), + dot: (a, b) => dot2(a, b), + exp: (a) => exp2([], a), + exp2: (a) => exp_22([], a), + faceForward: (a, b, c) => faceForward([], a, b, c), + floor: (a) => floor2([], a), + fract: (a) => fract2([], a), + fwidth: () => ZERO2, + inc: (a) => addN2([], a, 1), + inversesqrt: (a) => invSqrt2([], a), + length: mag, + log: (a) => log2([], a), + log2: (a) => log_22([], a), + max: (a, b) => max2([], a, b), + min: (a, b) => min2([], a, b), + mix: (a, b, t) => mix2([], a, b, t), + mixn: (a, b, t) => mixN2([], a, b, t), + mod: (a, b) => fmod2([], a, b), + modn: (a, b) => fmodN2([], a, b), + mul: (a, b) => mul2([], a, b), + mulnv: (a, b) => mulN2([], b, a), + mulvn: (a, b) => mulN2([], a, b), + normalize: (a) => normalize([], a), + pow: (a, b) => pow2([], a, b), + radians: (a) => radians2([], a), + reflect: (a, b) => reflect([], a, b), + refract: (a, b, c) => refract([], a, b, c), + sign: (a) => sign2([], a), + sin: (a) => sin2([], a), + smoothstep: (a, b, t) => smoothStep2([], a, b, t), + sqrt: (a) => sqrt2([], a), + step: (a, b) => step2([], a, b), + sub: (a, b) => sub2([], a, b), + sub1: (a) => neg([], a), + subnv: (a, b) => sub2(null, [a, a], b), + subvn: (a, b) => subN2([], a, b), + tan: (a) => tan2([], a) +}; diff --git a/packages/shader-ast-js/src/env/vec3.ts b/packages/shader-ast-js/src/env/vec3.ts new file mode 100644 index 0000000000..e475d4fdb8 --- /dev/null +++ b/packages/shader-ast-js/src/env/vec3.ts @@ -0,0 +1,110 @@ +import { + abs3, + acos3, + add3, + addN3, + asin3, + atan_23, + atan3, + ceil3, + clamp3, + cos3, + cross3, + degrees3, + dist, + div3, + divN3, + dot3, + exp_23, + exp3, + faceForward, + floor3, + fmod3, + fmodN3, + fract3, + invSqrt3, + log_23, + log3, + mag, + max3, + min3, + mix3, + mixN3, + mul3, + mulN3, + neg, + normalize, + pow3, + radians3, + reflect, + refract, + sign3, + sin3, + smoothStep3, + sqrt3, + step3, + sub3, + subN3, + tan3, + ZERO3 +} from "@thi.ng/vectors"; +import { JSBuiltinsVec3 } from "../api"; + +export const VEC3: JSBuiltinsVec3 = { + abs: (a) => abs3([], a), + acos: (a) => acos3([], a), + add: (a, b) => add3([], a, b), + addnv: (a, b) => addN3([], b, a), + addvn: (a, b) => addN3([], a, b), + asin: (a) => asin3([], a), + atan: (a) => atan3([], a), + atannn: (a, b) => atan_23([], a, b), + ceil: (a) => ceil3([], a), + clamp: (x, a, b) => clamp3([], x, a, b), + cos: (a) => cos3([], a), + cross: (a, b) => cross3([], a, b), + dec: (a) => subN3([], a, 1), + degrees: (a) => degrees3([], a), + dFdx: () => ZERO3, + dFdy: () => ZERO3, + distance: dist, + div: (a, b) => div3([], a, b), + divnv: (a, b) => div3(null, [a, a, a], b), + divvn: (a, b) => divN3([], a, b), + dot: (a, b) => dot3(a, b), + exp: (a) => exp3([], a), + exp2: (a) => exp_23([], a), + faceForward: (a, b, c) => faceForward([], a, b, c), + floor: (a) => floor3([], a), + fract: (a) => fract3([], a), + fwidth: () => ZERO3, + inc: (a) => addN3([], a, 1), + inversesqrt: (a) => invSqrt3([], a), + length: mag, + log: (a) => log3([], a), + log2: (a) => log_23([], a), + max: (a, b) => max3([], a, b), + min: (a, b) => min3([], a, b), + mix: (a, b, t) => mix3([], a, b, t), + mixn: (a, b, t) => mixN3([], a, b, t), + mod: (a, b) => fmod3([], a, b), + modn: (a, b) => fmodN3([], a, b), + mul: (a, b) => mul3([], a, b), + mulnv: (a, b) => mulN3([], b, a), + mulvn: (a, b) => mulN3([], a, b), + normalize: (a) => normalize([], a), + pow: (a, b) => pow3([], a, b), + radians: (a) => radians3([], a), + reflect: (a, b) => reflect([], a, b), + refract: (a, b, c) => refract([], a, b, c), + sign: (a) => sign3([], a), + sin: (a) => sin3([], a), + smoothstep: (a, b, t) => smoothStep3([], a, b, t), + sqrt: (a) => sqrt3([], a), + step: (a, b) => step3([], a, b), + sub: (a, b) => sub3([], a, b), + sub1: (a) => neg([], a), + subnv: (a, b) => sub3(null, [a, a, a], b), + subvn: (a, b) => subN3([], a, b), + tan: (a) => tan3([], a) +}; diff --git a/packages/shader-ast-js/src/env/vec4.ts b/packages/shader-ast-js/src/env/vec4.ts new file mode 100644 index 0000000000..ef2479bfbc --- /dev/null +++ b/packages/shader-ast-js/src/env/vec4.ts @@ -0,0 +1,108 @@ +import { + abs4, + acos4, + add4, + addN4, + asin4, + atan_24, + atan4, + ceil4, + clamp4, + cos4, + degrees4, + dist, + div4, + divN4, + dot4, + exp_24, + exp4, + faceForward, + floor4, + fmod4, + fmodN4, + fract4, + invSqrt4, + log_24, + log4, + mag, + max4, + min4, + mix4, + mixN4, + mul4, + mulN4, + neg, + normalize, + pow4, + radians4, + reflect, + refract, + sign4, + sin4, + smoothStep4, + sqrt4, + step4, + sub4, + subN4, + tan4, + ZERO4 +} from "@thi.ng/vectors"; +import { JSBuiltinsVec } from "../api"; + +export const VEC4: JSBuiltinsVec = { + abs: (a) => abs4([], a), + acos: (a) => acos4([], a), + add: (a, b) => add4([], a, b), + addnv: (a, b) => addN4([], b, a), + addvn: (a, b) => addN4([], a, b), + asin: (a) => asin4([], a), + atan: (a) => atan4([], a), + atannn: (a, b) => atan_24([], a, b), + ceil: (a) => ceil4([], a), + clamp: (x, a, b) => clamp4([], x, a, b), + cos: (a) => cos4([], a), + dec: (a) => subN4([], a, 1), + degrees: (a) => degrees4([], a), + dFdx: () => ZERO4, + dFdy: () => ZERO4, + distance: dist, + div: (a, b) => div4([], a, b), + divnv: (a, b) => div4(null, [a, a, a, a], b), + divvn: (a, b) => divN4([], a, b), + dot: (a, b) => dot4(a, b), + exp: (a) => exp4([], a), + exp2: (a) => exp_24([], a), + faceForward: (a, b, c) => faceForward([], a, b, c), + floor: (a) => floor4([], a), + fract: (a) => fract4([], a), + fwidth: () => ZERO4, + inc: (a) => addN4([], a, 1), + inversesqrt: (a) => invSqrt4([], a), + length: mag, + log: (a) => log4([], a), + log2: (a) => log_24([], a), + max: (a, b) => max4([], a, b), + min: (a, b) => min4([], a, b), + mix: (a, b, t) => mix4([], a, b, t), + mixn: (a, b, t) => mixN4([], a, b, t), + mod: (a, b) => fmod4([], a, b), + modn: (a, b) => fmodN4([], a, b), + mul: (a, b) => mul4([], a, b), + mulnv: (a, b) => mulN4([], b, a), + mulvn: (a, b) => mulN4([], a, b), + normalize: (a) => normalize([], a), + pow: (a, b) => pow4([], a, b), + radians: (a) => radians4([], a), + reflect: (a, b) => reflect([], a, b), + refract: (a, b, c) => refract([], a, b, c), + sign: (a) => sign4([], a), + sin: (a) => sin4([], a), + smoothstep: (a, b, t) => smoothStep4([], a, b, t), + sqrt: (a) => sqrt4([], a), + step: (a, b) => step4([], a, b), + sub: (a, b) => sub4([], a, b), + sub1: (a) => neg([], a), + subnv: (a, b) => sub4(null, [a, a, a, a], b), + subvn: (a, b) => subN4([], a, b), + tan: (a) => tan4([], a) +}; diff --git a/packages/shader-ast-js/src/index.ts b/packages/shader-ast-js/src/index.ts index 1434fa24f7..6c670b4828 100644 --- a/packages/shader-ast-js/src/index.ts +++ b/packages/shader-ast-js/src/index.ts @@ -1,2 +1,4 @@ +export * from "./api"; +export * from "./env"; export * from "./runtime"; export * from "./target"; diff --git a/packages/shader-ast-js/src/runtime.ts b/packages/shader-ast-js/src/runtime.ts index 1acb086b1b..41b51f5c19 100644 --- a/packages/shader-ast-js/src/runtime.ts +++ b/packages/shader-ast-js/src/runtime.ts @@ -1,32 +1,137 @@ -import { Fn } from "@thi.ng/api"; -import { clamp01 } from "@thi.ng/math"; +import { assert, Fn } from "@thi.ng/api"; +import { clamp, clamp01 } from "@thi.ng/math"; +import { ABGR8888, PackedBuffer } from "@thi.ng/pixel"; +import { ReadonlyVec, Vec } from "@thi.ng/vectors"; -export interface RuntimeOpts { - canvas: HTMLCanvasElement; -} -export const initRuntime = (opts: RuntimeOpts) => { - const W = opts.canvas.width; - const H = opts.canvas.height - 1; - const ctx = opts.canvas.getContext("2d")!; - const img = ctx.getImageData(0, 0, W, H); - const u32 = new Uint32Array(img.data.buffer); - return { - update(fn: Fn) { - const frag = []; - for (let y = 0, i = 0; y <= H; y++) { - frag[1] = H - y; - for (let x = 0; x < W; x++) { - frag[0] = x; - u32[i++] = rgba2bgra(fn(frag)); - } - } - ctx.putImageData(img, 0, 0); +const rgba2bgra = (rgba: ReadonlyVec) => + ((clamp01(rgba[0]) * 255.5) << 0) | + ((clamp01(rgba[1]) * 255.5) << 8) | + ((clamp01(rgba[2]) * 255.5) << 16) | + ((clamp01(rgba[3]) * 255.5) << 24); + +const clampCoord = (x: number, maxW: number, w?: number) => + w !== undefined ? Math.min(x + w, maxW) : maxW; + +/** + * Low-level function used by `canvasRenderer()` and `renderToBuffer()`. + * Applies shader function `fn` to each pixel in the given region of the + * `u32` raw ABGR buffer (a `Uint32Array`). The region is defined by the + * top-left `x`, `y` coords and `w`, `h` dimensions. The remaining + * parameters `bufW`, `bufH`, `bufOffsetX`, `bufOffsetY` and `imgH` are + * used to define the actual location of the given buffer in the full + * image to be computed and to support use cases where the target array + * only defines a sub-region of the full image (e.g. when splitting + * rendering over multiple workers, each with their own buffer). + * + * @param fn + * @param u32 + * @param bufW + * @param bufH + * @param x + * @param y + * @param w + * @param h + * @param bufOffsetX + * @param bufOffsetY + * @param imgH + */ +export const renderPixels = ( + fn: Fn, + u32: Uint32Array, + bufW: number, + bufH: number, + x: number, + y: number, + w?: number, + h?: number, + bufOffsetX = 0, + bufOffsetY = 0, + imgH = bufH +) => { + const frag = []; + x = clamp(x, 0, bufW); + y = clamp(y, 0, bufH); + const x2 = clampCoord(x, bufW, w); + const y2 = clampCoord(y, bufH, h); + for (let yy = y; yy < y2; yy++) { + frag[1] = imgH - 1 - yy - bufOffsetY; + let i = yy * bufW + x; + for (let xx = x; xx < x2; xx++) { + frag[0] = xx + bufOffsetX; + u32[i++] = rgba2bgra(fn(frag)); } - }; + } + return u32; }; -const rgba2bgra = (rgba: number[]) => - ((clamp01(rgba[0]) * 0xff) << 0) | - ((clamp01(rgba[1]) * 0xff) << 8) | - ((clamp01(rgba[2]) * 0xff) << 16) | - ((clamp01(rgba[3]) * 0xff) << 24); +/** + * Takes a `PackedBuffer` pixel buffer from thi.ng/pixel w/ `ABGR8888` + * format, an optional buffer local region defined by `x`, `y`, `w`, `h` + * and applies shader function `fn` to each pixel in that region (or + * full buffer by default). + * + * In case the buffer only defines a sub-region of a larger image, + * `bufOffsetX`, `bufOffsetY` and `imgH` can be given to configure the + * location and full image height. + * + * @param fn + * @param buf + * @param x + * @param y + * @param w + * @param h + * @param bufOffsetX + * @param bufOffsetY + * @param imgH + */ +export const renderBuffer = ( + fn: Fn, + buf: PackedBuffer, + x = 0, + y = 0, + w?: number, + h?: number, + bufOffsetX = 0, + bufOffsetY = 0, + imgH = buf.height +) => { + assert(buf.format === ABGR8888, `invalid buffer pixel format`); + renderPixels( + fn, + buf.pixels, + buf.width, + buf.height, + x, + y, + w, + h, + bufOffsetX, + bufOffsetY, + imgH + ); + return buf; +}; + +/** + * Higher order function accepting an `HTMLCanvasElement` and returning + * a render function which accepts the following parameters: + * + * - `fn` - shader function (compiled via `targetJS().compile(ast)`) + * - `x`, `y`, `w`, `h` - optional args to define a sub-region to be + * updated (default to full image update) + * + * @param canvas + */ +export const canvasRenderer = (canvas: HTMLCanvasElement) => { + const buf = PackedBuffer.fromCanvas(canvas); + return ( + fn: Fn, + x = 0, + y = 0, + w = canvas.width, + h = canvas.height + ) => { + renderBuffer(fn, buf, x, y, w, h); + buf.blitCanvas(canvas); + }; +}; diff --git a/packages/shader-ast-js/src/target.ts b/packages/shader-ast-js/src/target.ts index d610d03b9e..84a9804176 100644 --- a/packages/shader-ast-js/src/target.ts +++ b/packages/shader-ast-js/src/target.ts @@ -1,65 +1,6 @@ -import { - Fn, - Fn2, - Fn3, - Fn4, - Fn5, - Fn6 -} from "@thi.ng/api"; +import { Fn } from "@thi.ng/api"; import { isBoolean, isNumber } from "@thi.ng/checks"; import { unsupported } from "@thi.ng/errors"; -import { - clamp, - deg, - fmod, - fract, - mix, - rad, - smoothStep, - step -} from "@thi.ng/math"; -import { - add22, - add33, - add44, - addN22, - addN33, - addN44, - div22, - div33, - div44, - divN22, - divN33, - divN44, - Mat, - mat22n, - mat22v, - mat33n, - mat33v, - mat44n, - mat44v, - mul22, - mul33, - mul44, - mulM22, - mulM33, - mulM44, - mulN22, - mulN33, - mulN44, - mulV22, - mulV33, - mulV44, - mulVM22, - mulVM33, - mulVM44, - sub22, - sub33, - sub44, - subN22, - subN33, - subN44 -} from "@thi.ng/matrices"; import { defTarget, Func, @@ -75,1007 +16,95 @@ import { Sym, Term } from "@thi.ng/shader-ast"; -import { - abs2, - abs3, - abs4, - acos2, - acos3, - acos4, - add2, - add3, - add4, - addI2, - addI3, - addI4, - addN2, - addN3, - addN4, - addNI2, - addNI3, - addNI4, - addNU2, - addNU3, - addNU4, - addU2, - addU3, - addU4, - asin2, - asin3, - asin4, - atan2, - atan3, - atan4, - bitAndI2, - bitAndI3, - bitAndI4, - bitAndU2, - bitAndU3, - bitAndU4, - bitNotI2, - bitNotI3, - bitNotI4, - bitNotU2, - bitNotU3, - bitNotU4, - bitOrI2, - bitOrI3, - bitOrI4, - bitOrU2, - bitOrU3, - bitOrU4, - bitXorI2, - bitXorI3, - bitXorI4, - bitXorU2, - bitXorU3, - bitXorU4, - ceil2, - ceil3, - ceil4, - clamp2, - clamp3, - clamp4, - cos2, - cos3, - cos4, - cross3, - degrees2, - degrees3, - degrees4, - dist, - div2, - div3, - div4, - divI2, - divI3, - divI4, - divN2, - divN3, - divN4, - divNI2, - divNI3, - divNI4, - divNU2, - divNU3, - divNU4, - divU2, - divU3, - divU4, - dot2, - dot3, - dot4, - exp_22, - exp_23, - exp_24, - exp2, - exp3, - exp4, - faceForward, - floor2, - floor3, - floor4, - fmod2, - fmod3, - fmod4, - fmodN2, - fmodN3, - fmodN4, - fract2, - fract3, - fract4, - invSqrt2, - invSqrt3, - invSqrt4, - log_22, - log_23, - log_24, - log2, - log3, - log4, - lshiftI2, - lshiftI3, - lshiftI4, - lshiftU2, - lshiftU3, - lshiftU4, - mag, - max2, - max3, - max4, - min2, - min3, - min4, - mix2, - mix3, - mix4, - mixN2, - mixN3, - mixN4, - mod2, - mod3, - mod4, - modN2, - modN3, - modN4, - mul2, - mul3, - mul4, - mulI2, - mulI3, - mulI4, - mulN2, - mulN3, - mulN4, - mulNI2, - mulNI3, - mulNI4, - mulNU2, - mulNU3, - mulNU4, - mulU2, - mulU3, - mulU4, - neg, - normalize, - pow2, - pow3, - pow4, - radians2, - radians3, - radians4, - reflect, - refract, - rshiftI2, - rshiftI3, - rshiftI4, - rshiftU2, - rshiftU3, - rshiftU4, - setSwizzle2, - setSwizzle3, - setSwizzle4, - setVN3, - setVN4, - setVV4, - sign2, - sign3, - sign4, - sin2, - sin3, - sin4, - smoothStep2, - smoothStep3, - smoothStep4, - sqrt2, - sqrt3, - sqrt4, - step2, - step3, - step4, - sub2, - sub3, - sub4, - subI2, - subI3, - subI4, - subN2, - subN3, - subN4, - subNI2, - subNI3, - subNI4, - subNU2, - subNU3, - subNU4, - subU2, - subU3, - subU4, - swizzle2, - swizzle3, - swizzle4, - tan2, - tan3, - tan4, - Vec, - vecOf, - ZERO2, - ZERO3, - ZERO4 -} from "@thi.ng/vectors"; - -export interface JSTarget extends Fn, string> { - /** - * Compiles given AST to JavaScript, using optional `env` as backend - * for various operators / builtins. If `env` is not given the - * bundled `JS_DEFAULT_ENV` is used (based on thi.ng/vectors and - * thi.ng/matrices packages). - * - * Any functions defined in the given AST will be exported using - * their defined name via the returned object. - * - * ``` - * const js = targetJS(); - * const module = js.compile( - * defn("float", "foo", [["float"]], (x)=> [ret(mul(x, float(10)))]) - * ); - * - * module.foo(42) - * // 420 - * - * module.foo.toString() - * // function foo(_sym0) { - * // return (_sym0 * 10); - * // } - * ``` - * - * @param tree - * @param env - */ - compile(tree: Term, env?: JSEnv): any; -} - -export interface JSBuiltinsCommon { - abs: Fn; - clamp: Fn3; - max: Fn2; - min: Fn2; - sign: Fn; -} - -export interface JSBuiltinsMath { - sub1: Fn; - add: Fn2; - sub: Fn2; - mul: Fn2; - div: Fn2; - inc: Fn; - dec: Fn; -} - -export interface JSBuiltinsBinary { - bitand: Fn2; - lshift: Fn2; - bitnot1: Fn2; - bitor: Fn2; - rshift: Fn2; - bitxor: Fn2; -} - -export interface JSBuiltinsFloat extends JSBuiltinsCommon { - acos: Fn; - asin: Fn; - atan: Fn; - ceil: Fn; - cos: Fn; - degrees: Fn; - dFdx: Fn; - dFdy: Fn; - exp: Fn; - exp2: Fn; - floor: Fn; - fract: Fn; - fwidth: Fn; - inversesqrt: Fn; - log: Fn; - log2: Fn; - mix: Fn3; - mixn: Fn3; - mod: Fn2; - modn: Fn2; - pow: Fn2; - radians: Fn; - sin: Fn; - smoothstep: Fn3; - sqrt: Fn; - step: Fn2; - tan: Fn; -} - -export interface JSBuiltinsInt - extends JSBuiltinsCommon, - JSBuiltinsMath, - JSBuiltinsBinary { - modi: Fn2; -} - -export interface JSBuiltinsVecScalar { - addvn: Fn2; - subvn: Fn2; - mulvn: Fn2; - divvn: Fn2; - addnv: Fn2; - subnv: Fn2; - mulnv: Fn2; - divnv: Fn2; -} - -export interface JSBuiltinsVec - extends JSBuiltinsFloat, - JSBuiltinsMath, - JSBuiltinsVecScalar { - distance: Fn2; - dot: Fn2; - faceForward: Fn3; - length: Fn; - normalize: Fn; - reflect: Fn2; - refract: Fn3; -} - -export interface JSBuiltinsVec3 extends JSBuiltinsVec { - cross: Fn2; -} - -export interface JSBuiltinsIntVec - extends JSBuiltinsInt, - JSBuiltinsVecScalar, - JSBuiltinsBinary { - modivn: Fn2; - modinv: Fn2; -} - -export interface JSBuiltinsMat - extends JSBuiltinsMath, - JSBuiltinsVecScalar { - mulm: Fn2; - mulvm: Fn2; - mulmv: Fn2; -} - -export interface JSBuiltinsSampler { - texelFetch: Fn3; - texelFetchOffset: Fn4; - texture: Fn3; - texturen: Fn3; - textureGrad: Fn4; - textureGradn: Fn4; - textureLod: Fn3; - textureLodn: Fn3; - textureOffset: Fn4; - textureOffsetn: Fn4; - textureProj: Fn3; - textureProjn: Fn3; - textureSize: Fn2; -} - -export interface JSEnv { - vec2n: Fn; - vec3n: Fn; - vec3vn: Fn2; - vec4n: Fn; - vec4vn: Fn2; - vec4vnn: Fn3; - vec4vv: Fn2; - mat2n: Fn; - mat2vv: Fn2; - mat3n: Fn; - mat3vvv: Fn3; - mat4n: Fn; - mat4vvvv: Fn4; - // swizzle1: Fn2; - swizzle2: Fn3; - swizzle3: Fn4; - swizzle4: Fn5; - // set_swizzle1: Fn3; - set_swizzle2: Fn4; - set_swizzle3: Fn5; - set_swizzle4: Fn6; - float: JSBuiltinsFloat; - int: JSBuiltinsInt; - uint: JSBuiltinsInt; - vec2: JSBuiltinsVec; - vec3: JSBuiltinsVec3; - vec4: JSBuiltinsVec; - ivec2: JSBuiltinsIntVec; - ivec3: JSBuiltinsIntVec; - ivec4: JSBuiltinsIntVec; - uvec2: JSBuiltinsIntVec; - uvec3: JSBuiltinsIntVec; - uvec4: JSBuiltinsIntVec; - mat2: JSBuiltinsMat; - mat3: JSBuiltinsMat; - mat4: JSBuiltinsMat; - sampler1D: JSBuiltinsSampler; - sampler2D: JSBuiltinsSampler; - sampler3D: JSBuiltinsSampler; - samplerCube: JSBuiltinsSampler; - sampler2DShadow: JSBuiltinsSampler; - samplerCubeShadow: JSBuiltinsSampler; -} - -// TODO texture lookups -// all texture fns currently return [0,0,0,0] or 0 -const SAMPLER_TODO: JSBuiltinsSampler = { - texelFetch: () => ZERO4, - texelFetchOffset: () => ZERO4, - texture: () => ZERO4, - texturen: () => 0, - textureGrad: () => ZERO4, - textureGradn: () => 0, - textureLod: () => ZERO4, - textureLodn: () => 0, - textureOffset: () => ZERO4, - textureOffsetn: () => 0, - textureProj: () => ZERO4, - textureProjn: () => 0, - textureSize: () => ZERO3 -}; - -const env: Partial = { - vec2n: (n) => [n, n], - vec3n: (n) => [n, n, n], - vec4n: (n) => [n, n, n, n], - vec3vn: (a, n) => setVN3([], a, n), - vec4vn: (a, n) => setVN4([], a, n), - vec4vnn: (a, z, w) => setVV4([], a, [z, w]), - vec4vv: (a, b) => setVV4([], a, b), - mat2n: (n) => mat22n([], n), - mat2vv: (a, b) => mat22v([], a, b), - mat3n: (n) => mat33n([], n), - mat3vvv: (a, b, c) => mat33v([], a, b, c), - mat4n: (n) => mat44n([], n), - mat4vvvv: (a, b, c, d) => mat44v([], a, b, c, d), - swizzle2: (a, b, c) => swizzle2([], a, b, c), - swizzle3: (a, b, c, d) => swizzle3([], a, b, c, d), - swizzle4: (a, b, c, d, e) => swizzle4([], a, b, c, d, e), - set_swizzle2: setSwizzle2, - set_swizzle3: setSwizzle3, - set_swizzle4: setSwizzle4, - float: { - abs: Math.abs, - acos: Math.acos, - asin: Math.asin, - atan: Math.atan, - ceil: Math.ceil, - clamp, - cos: Math.cos, - degrees: deg, - dFdx: () => 0, - dFdy: () => 0, - exp: Math.exp, - exp2: (x) => Math.pow(2, x), - floor: Math.floor, - fract, - fwidth: () => 0, - inversesqrt: (x) => 1 / Math.sqrt(x), - log: Math.log, - log2: Math.log2, - max: Math.max, - min: Math.min, - mix, - mixn: mix, - mod: fmod, - modn: fmod, - pow: Math.pow, - radians: rad, - sign: Math.sign, - sin: Math.sin, - smoothstep: smoothStep, - sqrt: Math.sqrt, - step, - tan: Math.tan - }, - int: { - abs: Math.abs, - add: (a, b) => (a + b) | 0, - bitand: (a, b) => a & b, - bitnot1: (a) => ~a, - bitor: (a, b) => a | b, - bitxor: (a, b) => a ^ b, - clamp, - dec: (a) => (a - 1) | 0, - div: (a, b) => (a / b) | 0, - inc: (a) => (a + 1) | 0, - lshift: (a, b) => a << b, - max: Math.max, - min: Math.min, - modi: (a, b) => a % b, - mul: (a, b) => (a * b) | 0, - rshift: (a, b) => a >> b, - sign: Math.sign, - sub: (a, b) => (a - b) | 0, - sub1: (a) => -a | 0 - }, - uint: { - abs: Math.abs, - add: (a, b) => (a + b) >>> 0, - bitand: (a, b) => (a & b) >>> 0, - bitnot1: (a) => ~a >>> 0, - bitor: (a, b) => (a | b) >>> 0, - bitxor: (a, b) => (a ^ b) >>> 0, - clamp, - dec: (a) => (a - 1) >>> 0, - div: (a, b) => (a / b) >>> 0, - inc: (a) => (a + 1) >>> 0, - lshift: (a, b) => (a << b) >>> 0, - max: Math.max, - min: Math.min, - modi: (a, b) => a % b, - mul: (a, b) => (a * b) >>> 0, - rshift: (a, b) => a >>> b, - sign: Math.sign, - sub: (a, b) => (a - b) >>> 0, - sub1: (a) => -a >>> 0 - }, - vec2: { - abs: (a) => abs2([], a), - acos: (a) => acos2([], a), - add: (a, b) => add2([], a, b), - addnv: (a, b) => addN2([], b, a), - addvn: (a, b) => addN2([], a, b), - asin: (a) => asin2([], a), - atan: (a) => atan2([], a), - ceil: (a) => ceil2([], a), - clamp: (x, a, b) => clamp2([], x, a, b), - cos: (a) => cos2([], a), - dec: (a) => subN2([], a, 1), - degrees: (a) => degrees2([], a), - dFdx: () => ZERO2, - dFdy: () => ZERO2, - distance: dist, - div: (a, b) => div2([], a, b), - divnv: (a, b) => mulN2([], b, 1 / a), - divvn: (a, b) => divN2([], a, b), - dot: (a, b) => dot2(a, b), - exp: (a) => exp2([], a), - exp2: (a) => exp_22([], a), - faceForward: (a, b, c) => faceForward([], a, b, c), - floor: (a) => floor2([], a), - fract: (a) => fract2([], a), - fwidth: () => ZERO2, - inc: (a) => addN2([], a, 1), - inversesqrt: (a) => invSqrt2([], a), - length: mag, - log: (a) => log2([], a), - log2: (a) => log_22([], a), - max: (a, b) => max2([], a, b), - min: (a, b) => min2([], a, b), - mix: (a, b, t) => mix2([], a, b, t), - mixn: (a, b, t) => mixN2([], a, b, t), - mod: (a, b) => fmod2([], a, b), - modn: (a, b) => fmodN2([], a, b), - mul: (a, b) => mul2([], a, b), - mulnv: (a, b) => mulN2([], b, a), - mulvn: (a, b) => mulN2([], a, b), - normalize: (a) => normalize([], a), - pow: (a, b) => pow2([], a, b), - radians: (a) => radians2([], a), - reflect: (a, b) => reflect([], a, b), - refract: (a, b, c) => refract([], a, b, c), - sign: (a) => sign2([], a), - sin: (a) => sin2([], a), - smoothstep: (a, b, t) => smoothStep2([], a, b, t), - sqrt: (a) => sqrt2([], a), - step: (a, b) => step2([], a, b), - sub: (a, b) => sub2([], a, b), - sub1: (a) => neg([], a), - subnv: (a, b) => sub2(null, [a, a], b), - subvn: (a, b) => subN2([], a, b), - tan: (a) => tan2([], a) - }, - vec3: { - abs: (a) => abs3([], a), - acos: (a) => acos3([], a), - add: (a, b) => add3([], a, b), - addnv: (a, b) => addN3([], b, a), - addvn: (a, b) => addN3([], a, b), - asin: (a) => asin3([], a), - atan: (a) => atan3([], a), - ceil: (a) => ceil3([], a), - clamp: (x, a, b) => clamp3([], x, a, b), - cos: (a) => cos3([], a), - cross: (a, b) => cross3([], a, b), - dec: (a) => subN3([], a, 1), - degrees: (a) => degrees3([], a), - dFdx: () => ZERO3, - dFdy: () => ZERO3, - distance: dist, - div: (a, b) => div3([], a, b), - divnv: (a, b) => mulN3([], b, 1 / a), - divvn: (a, b) => divN3([], a, b), - dot: (a, b) => dot3(a, b), - exp: (a) => exp3([], a), - exp2: (a) => exp_23([], a), - faceForward: (a, b, c) => faceForward([], a, b, c), - floor: (a) => floor3([], a), - fract: (a) => fract3([], a), - fwidth: () => ZERO3, - inc: (a) => addN3([], a, 1), - inversesqrt: (a) => invSqrt3([], a), - length: mag, - log: (a) => log3([], a), - log2: (a) => log_23([], a), - max: (a, b) => max3([], a, b), - min: (a, b) => min3([], a, b), - mix: (a, b, t) => mix3([], a, b, t), - mixn: (a, b, t) => mixN3([], a, b, t), - mod: (a, b) => fmod3([], a, b), - modn: (a, b) => fmodN3([], a, b), - mul: (a, b) => mul3([], a, b), - mulnv: (a, b) => mulN3([], b, a), - mulvn: (a, b) => mulN3([], a, b), - normalize: (a) => normalize([], a), - pow: (a, b) => pow3([], a, b), - radians: (a) => radians3([], a), - reflect: (a, b) => reflect([], a, b), - refract: (a, b, c) => refract([], a, b, c), - sign: (a) => sign3([], a), - sin: (a) => sin3([], a), - smoothstep: (a, b, t) => smoothStep3([], a, b, t), - sqrt: (a) => sqrt3([], a), - step: (a, b) => step3([], a, b), - sub: (a, b) => sub3([], a, b), - sub1: (a) => neg([], a), - subnv: (a, b) => sub3(null, [a, a, a], b), - subvn: (a, b) => subN3([], a, b), - tan: (a) => tan3([], a) - }, - vec4: { - abs: (a) => abs4([], a), - acos: (a) => acos4([], a), - add: (a, b) => add4([], a, b), - addnv: (a, b) => addN4([], b, a), - addvn: (a, b) => addN4([], a, b), - asin: (a) => asin4([], a), - atan: (a) => atan4([], a), - ceil: (a) => ceil4([], a), - clamp: (x, a, b) => clamp4([], x, a, b), - cos: (a) => cos4([], a), - dec: (a) => subN4([], a, 1), - degrees: (a) => degrees4([], a), - dFdx: () => ZERO4, - dFdy: () => ZERO4, - distance: dist, - div: (a, b) => div4([], a, b), - divnv: (a, b) => mulN4([], b, 1 / a), - divvn: (a, b) => divN4([], a, b), - dot: (a, b) => dot4(a, b), - exp: (a) => exp4([], a), - exp2: (a) => exp_24([], a), - faceForward: (a, b, c) => faceForward([], a, b, c), - floor: (a) => floor4([], a), - fract: (a) => fract4([], a), - fwidth: () => ZERO4, - inc: (a) => addN4([], a, 1), - inversesqrt: (a) => invSqrt4([], a), - length: mag, - log: (a) => log4([], a), - log2: (a) => log_24([], a), - max: (a, b) => max4([], a, b), - min: (a, b) => min4([], a, b), - mix: (a, b, t) => mix4([], a, b, t), - mixn: (a, b, t) => mixN4([], a, b, t), - mod: (a, b) => fmod4([], a, b), - modn: (a, b) => fmodN4([], a, b), - mul: (a, b) => mul4([], a, b), - mulnv: (a, b) => mulN4([], b, a), - mulvn: (a, b) => mulN4([], a, b), - normalize: (a) => normalize([], a), - pow: (a, b) => pow4([], a, b), - radians: (a) => radians4([], a), - reflect: (a, b) => reflect([], a, b), - refract: (a, b, c) => refract([], a, b, c), - sign: (a) => sign4([], a), - sin: (a) => sin4([], a), - smoothstep: (a, b, t) => smoothStep4([], a, b, t), - sqrt: (a) => sqrt4([], a), - step: (a, b) => step4([], a, b), - sub: (a, b) => sub4([], a, b), - sub1: (a) => neg([], a), - subnv: (a, b) => sub4(null, [a, a, a, a], b), - subvn: (a, b) => subN4([], a, b), - tan: (a) => tan4([], a) - }, - mat2: { - add: (a, b) => add22([], a, b), - addnv: (a, b) => addN22([], b, a), - addvn: (a, b) => addN22([], a, b), - dec: (a) => subN22([], a, 1), - div: (a, b) => div22([], a, b), - divnv: (a, b) => mulN22([], b, 1 / a), - divvn: (a, b) => divN22([], a, b), - inc: (a) => addN22([], a, 1), - mul: (a, b) => mul22([], a, b), - mulm: (a, b) => mulM22([], a, b), - mulmv: (a, b) => mulV22([], a, b), - mulnv: (a, b) => mulN22([], b, a), - mulvm: (a, b) => mulVM22([], a, b), - mulvn: (a, b) => mulN22([], a, b), - sub: (a, b) => sub22([], a, b), - sub1: (a) => neg([], a), - subnv: (a, b) => sub22(null, [a, a, a, a], b), - subvn: (a, b) => subN22([], a, b) - }, - mat3: { - add: (a, b) => add33([], a, b), - addnv: (a, b) => addN33([], b, a), - addvn: (a, b) => addN33([], a, b), - dec: (a) => subN33([], a, 1), - div: (a, b) => div33([], a, b), - divnv: (a, b) => mulN33([], b, 1 / a), - divvn: (a, b) => divN33([], a, b), - inc: (a) => addN33([], a, 1), - mul: (a, b) => mul33([], a, b), - mulm: (a, b) => mulM33([], a, b), - mulmv: (a, b) => mulV33([], a, b), - mulnv: (a, b) => mulN33([], b, a), - mulvm: (a, b) => mulVM33([], a, b), - mulvn: (a, b) => mulN33([], a, b), - sub: (a, b) => sub33([], a, b), - sub1: (a) => neg([], a), - subnv: (a, b) => sub33(null, vecOf(9, a), b), - subvn: (a, b) => subN33([], a, b) - }, - mat4: { - add: (a, b) => add44([], a, b), - addnv: (a, b) => addN44([], b, a), - addvn: (a, b) => addN44([], a, b), - dec: (a) => subN44([], a, 1), - div: (a, b) => div44([], a, b), - divnv: (a, b) => mulN44([], b, 1 / a), - divvn: (a, b) => divN44([], a, b), - inc: (a) => addN44([], a, 1), - mul: (a, b) => mul44([], a, b), - mulm: (a, b) => mulM44([], a, b), - mulmv: (a, b) => mulV44([], a, b), - mulnv: (a, b) => mulN44([], b, a), - mulvm: (a, b) => mulVM44([], a, b), - mulvn: (a, b) => mulN44([], a, b), - sub: (a, b) => sub44([], a, b), - sub1: (a) => neg([], a), - subnv: (a, b) => sub44(null, vecOf(16, a), b), - subvn: (a, b) => subN44([], a, b) - }, - sampler1D: SAMPLER_TODO, - sampler2D: SAMPLER_TODO, - sampler3D: SAMPLER_TODO, - samplerCube: SAMPLER_TODO, - sampler2DShadow: SAMPLER_TODO, - samplerCubeShadow: SAMPLER_TODO +import { JSTarget } from "./api"; +import { JS_DEFAULT_ENV } from "./env"; + +const CMP_OPS: Partial> = { + "!": "not", + "<": "lt", + "<=": "lte", + "==": "eq", + "!=": "neq", + ">=": "gte", + ">": "gt" }; - -env.ivec2 = { - ...env.vec2!, - add: (a, b) => addI2([], a, b), - addvn: (a, b) => addNI2([], a, b), - addnv: (a, b) => addNI2([], b, a), - div: (a, b) => divI2([], a, b), - divvn: (a, b) => divNI2([], a, b), - divnv: (a, b) => mulNI2([], b, 1 / a), - modi: (a, b) => mod2([], a, b), - modivn: (a, b) => modN2([], a, b), - modinv: (a, b) => mod2([], [a, a], b), - mul: (a, b) => mulI2([], a, b), - mulvn: (a, b) => mulNI2([], a, b), - mulnv: (a, b) => mulNI2([], b, a), - sub: (a, b) => subI2([], a, b), - subvn: (a, b) => subNI2([], a, b), - subnv: (a, b) => subI2([], [a, a], b), - bitand: (a, b) => bitAndI2([], a, b), - lshift: (a, b) => lshiftI2([], a, b), - bitnot1: (a) => bitNotI2([], a), - bitor: (a, b) => bitOrI2([], a, b), - rshift: (a, b) => rshiftI2([], a, b), - bitxor: (a, b) => bitXorI2([], a, b) -}; - -env.ivec3 = { - ...env.vec3!, - add: (a, b) => addI3([], a, b), - addvn: (a, b) => addNI3([], a, b), - addnv: (a, b) => addNI3([], b, a), - div: (a, b) => divI3([], a, b), - divvn: (a, b) => divNI3([], a, b), - divnv: (a, b) => mulNI3([], b, 1 / a), - modi: (a, b) => mod3([], a, b), - modivn: (a, b) => modN3([], a, b), - modinv: (a, b) => mod3([], [a, a, a], b), - mul: (a, b) => mulI3([], a, b), - mulvn: (a, b) => mulNI3([], a, b), - mulnv: (a, b) => mulNI3([], b, a), - sub: (a, b) => subI3([], a, b), - subvn: (a, b) => subNI3([], a, b), - subnv: (a, b) => subI3([], [a, a, a], b), - bitand: (a, b) => bitAndI3([], a, b), - lshift: (a, b) => lshiftI3([], a, b), - bitnot1: (a) => bitNotI3([], a), - bitor: (a, b) => bitOrI3([], a, b), - rshift: (a, b) => rshiftI3([], a, b), - bitxor: (a, b) => bitXorI3([], a, b) -}; - -env.ivec4 = { - ...env.vec4!, - add: (a, b) => addI4([], a, b), - addvn: (a, b) => addNI4([], a, b), - addnv: (a, b) => addNI4([], b, a), - div: (a, b) => divI4([], a, b), - divvn: (a, b) => divNI4([], a, b), - divnv: (a, b) => mulNI4([], b, 1 / a), - modi: (a, b) => mod4([], a, b), - modivn: (a, b) => modN4([], a, b), - modinv: (a, b) => mod4([], [a, a, a, a], b), - mul: (a, b) => mulI4([], a, b), - mulvn: (a, b) => mulNI4([], a, b), - mulnv: (a, b) => mulNI4([], b, a), - sub: (a, b) => subI4([], a, b), - subvn: (a, b) => subNI4([], a, b), - subnv: (a, b) => subI4([], [a, a, a, a], b), - bitand: (a, b) => bitAndI4([], a, b), - lshift: (a, b) => lshiftI4([], a, b), - bitnot1: (a) => bitNotI4([], a), - bitor: (a, b) => bitOrI4([], a, b), - rshift: (a, b) => rshiftI4([], a, b), - bitxor: (a, b) => bitXorI4([], a, b) -}; - -env.uvec2 = { - ...env.vec2!, - add: (a, b) => addU2([], a, b), - addvn: (a, b) => addNU2([], a, b), - addnv: (a, b) => addNU2([], b, a), - div: (a, b) => divU2([], a, b), - divvn: (a, b) => divNU2([], a, b), - divnv: (a, b) => mulNU2([], b, 1 / a), - modi: (a, b) => mod2([], a, b), - modivn: (a, b) => modN2([], a, b), - modinv: (a, b) => mod2([], [a, a], b), - mul: (a, b) => mulU2([], a, b), - mulvn: (a, b) => mulNU2([], a, b), - mulnv: (a, b) => mulNU2([], b, a), - sub: (a, b) => subU2([], a, b), - subvn: (a, b) => subNU2([], a, b), - subnv: (a, b) => subU2([], [a, a], b), - bitand: (a, b) => bitAndU2([], a, b), - lshift: (a, b) => lshiftU2([], a, b), - bitnot1: (a) => bitNotU2([], a), - bitor: (a, b) => bitOrU2([], a, b), - rshift: (a, b) => rshiftU2([], a, b), - bitxor: (a, b) => bitXorU2([], a, b) -}; - -env.uvec3 = { - ...env.vec3!, - add: (a, b) => addU3([], a, b), - addvn: (a, b) => addNU3([], a, b), - addnv: (a, b) => addNU3([], b, a), - div: (a, b) => divU3([], a, b), - divvn: (a, b) => divNU3([], a, b), - divnv: (a, b) => mulNU3([], b, 1 / a), - modi: (a, b) => mod3([], a, b), - modivn: (a, b) => modN3([], a, b), - modinv: (a, b) => mod3([], [a, a, a], b), - mul: (a, b) => mulU3([], a, b), - mulvn: (a, b) => mulNU3([], a, b), - mulnv: (a, b) => mulNU3([], b, a), - sub: (a, b) => subU3([], a, b), - subvn: (a, b) => subNU3([], a, b), - subnv: (a, b) => subU3([], [a, a, a], b), - bitand: (a, b) => bitAndU3([], a, b), - lshift: (a, b) => lshiftU3([], a, b), - bitnot1: (a) => bitNotU3([], a), - bitor: (a, b) => bitOrU3([], a, b), - rshift: (a, b) => rshiftU3([], a, b), - bitxor: (a, b) => bitXorU3([], a, b) -}; - -env.uvec4 = { - ...env.vec4!, - add: (a, b) => addU4([], a, b), - addvn: (a, b) => addNU4([], a, b), - addnv: (a, b) => addNU4([], b, a), - div: (a, b) => divU4([], a, b), - divvn: (a, b) => divNU4([], a, b), - divnv: (a, b) => mulNU4([], b, 1 / a), - modi: (a, b) => mod4([], a, b), - modivn: (a, b) => modN4([], a, b), - modinv: (a, b) => mod4([], [a, a, a, a], b), - mul: (a, b) => mulU4([], a, b), - mulvn: (a, b) => mulNU4([], a, b), - mulnv: (a, b) => mulNU4([], b, a), - sub: (a, b) => subU4([], a, b), - subvn: (a, b) => subNU4([], a, b), - subnv: (a, b) => subU4([], [a, a, a, a], b), - bitand: (a, b) => bitAndU4([], a, b), - lshift: (a, b) => lshiftU4([], a, b), - bitnot1: (a) => bitNotU4([], a), - bitor: (a, b) => bitOrU4([], a, b), - rshift: (a, b) => rshiftU4([], a, b), - bitxor: (a, b) => bitXorU4([], a, b) +const OP_IDS: Record = { + ...(CMP_OPS), + "+": "add", + "-": "sub", + "*": "mul", + "/": "div", + "%": "modi", + "++": "inc", + "--": "dec", + "||": "or", + "&&": "and", + "|": "bitor", + "&": "bitand", + "^": "bitxor", + "~": "bitnot", + "<<": "lshift", + ">>": "rshift" }; -export const JS_DEFAULT_ENV = env; +const PRELUDE = [ + "float", + "int", + "uint", + "vec2", + "vec3", + "vec4", + "ivec2", + "ivec3", + "ivec4", + "uvec2", + "uvec3", + "uvec4", + "mat2", + "mat3", + "mat4", + "sampler2D", + "sampler3D", + "samplerCube", + "sampler2DShadow", + "samplerCubeShadow" +] + .map((x) => `const ${x} = env.${x};`) + .join("\n"); + +const COMPS: any = { x: 0, y: 1, z: 2, w: 3 }; + +const RE_SEMI = /[};]$/; + +const isIntOrBool = (l: Term) => isInt(l) || isUint(l) || isBool(l); + +const isVecOrMat = (l: Term) => isVec(l) || isMat(l); + +const swizzle = (id: string) => [...id].map((x) => COMPS[x]).join(", "); + +const buildComments = (t: Func) => + `/**\n${t.args.map((p) => ` * @param ${p.id} ${p.type}`).join("\n")}\n */`; + +const buildExports = (tree: Term) => + tree.tag === "scope" + ? (tree).body + .filter((x) => x.tag === "fn") + .map((f) => `${(>f).id}: ${(>f).id}`) + .join(",\n") + : tree.tag === "fn" + ? `${(>tree).id}: ${(>tree).id}` + : ""; export const targetJS = () => { - const CMP_OPS: Partial> = { - "!": "not", - "<": "lt", - "<=": "lte", - "==": "eq", - "!=": "neq", - ">=": "gte", - ">": "gt" - }; - const OP_IDS: Record = { - ...(CMP_OPS), - "+": "add", - "-": "sub", - "*": "mul", - "/": "div", - "%": "modi", - "++": "inc", - "--": "dec", - "||": "or", - "&&": "and", - "|": "bitor", - "&": "bitand", - "^": "bitxor", - "~": "bitnot", - "<<": "lshift", - ">>": "rshift" - }; - - const PRELUDE = - [ - "float", - "int", - "uint", - "vec2", - "vec3", - "vec4", - "ivec2", - "ivec3", - "ivec4", - "uvec2", - "uvec3", - "uvec4", - "mat2", - "mat3", - "mat4", - "sampler2D", - "sampler3D", - "samplerCube", - "sampler2DShadow", - "samplerCubeShadow" - ] - .map((x) => `const ${x} = env.${x};`) - .join("\n") + "\n"; - - const COMPS: any = { x: 0, y: 1, z: 2, w: 3 }; - - const RE_SEMI = /[};]$/; - const $list = (body: Term[], sep = ", ") => body.map(emit).join(sep); - const $docParam = (p: Sym) => ` * @param ${p.id} ${p.type}`; - const $fn = (name: string, args: Term[]) => `${name}(${$list(args)})`; const $vec = ({ val, info, type }: Lit) => !info ? `[${$list(val)}]` : `env.${type}${info}(${$list(val)})`; - const $swizzle = (id: string) => [...id].map((x) => COMPS[x]).join(", "); + const $num = (v: any, f: Fn) => + isNumber(v) ? String(v) : f(v); const emit: Fn, string> = defTarget({ arg: (t) => t.id, @@ -1083,15 +112,16 @@ export const targetJS = () => { array_init: (t) => `[${$list(t.init)}]`, assign: (t) => { + const rhs = emit(t.r); if (t.l.tag === "swizzle") { const s = >t.l; + const id = swizzle(s.id); + const val = emit(s.val); return s.id.length > 1 - ? `env.set_swizzle${s.id.length}(${emit(s.val)}, ${emit( - t.r - )}, ${$swizzle(s.id)})` - : `(${emit(s.val)}[${$swizzle(s.id)}] = ${emit(t.r)})`; + ? `env.set_swizzle${s.id.length}(${val}, ${rhs}, ${id})` + : `(${val}[${id}] = ${rhs})`; } - return emit(t.l) + " = " + emit(t.r); + return `${emit(t.l)} = ${rhs}`; }, ctrl: (t) => t.id, @@ -1102,22 +132,19 @@ export const targetJS = () => { decl: ({ type, id }) => { const res: string[] = []; - res.push(id.opts.const ? "const" : "let"); - res.push(`/*${type}*/`); - res.push(id.id); + res.push(id.opts.const ? "const" : "let", `/*${type}*/`, id.id); id.init - ? res.push("=", emit(id.init)) + ? res.push(`= ${emit(id.init)}`) : id.opts.num !== undefined - ? res.push("=", `new Array(${id.opts.num})`) + ? res.push(`= new Array(${id.opts.num})`) : undefined; return res.join(" "); }, fn: (t) => - "/**\n" + - t.args.map($docParam).join("\n") + - "\n */\n" + - `function ${t.id}(${$list(t.args)}) ${emit(t.scope)}`, + `${buildComments(t)}\nfunction ${t.id}(${$list(t.args)}) ${emit( + t.scope + )}`, for: (t) => `for(${t.init ? emit(t.init) : ""}; ${emit(t.test)}; ${ @@ -1127,8 +154,8 @@ export const targetJS = () => { idx: (t) => `${emit(t.val)}[${emit(t.id)}]`, if: (t) => { - const res = `if (${emit(t.test)}) ` + emit(t.t); - return t.f ? res + " else " + emit(t.f) : res; + const res = `if (${emit(t.test)}) ${emit(t.t)}`; + return t.f ? `${res} else ${emit(t.f)}` : res; }, lit: (t) => { @@ -1137,15 +164,13 @@ export const targetJS = () => { case "bool": return isBoolean(v) ? String(v) : `!!(${emit(v)})`; case "float": - return isNumber(v) - ? String(v) - : isBool(v) - ? `(${emit(v)} & 1)` - : emit(v); + return $num(v, () => + isBool(v) ? `(${emit(v)} & 1)` : emit(v) + ); case "int": - return isNumber(v) ? String(v) : `(${emit(v)} | 0)`; + return $num(v, () => `(${emit(v)} | 0)`); case "uint": - return isNumber(v) ? String(v) : `(${emit(v)} >>> 0)`; + return $num(v, () => `(${emit(v)} >>> 0)`); case "vec2": case "vec3": case "vec4": @@ -1168,31 +193,29 @@ export const targetJS = () => { }, op1: (t) => { - const complex = isVec(t) || isMat(t) || isInt(t); - if (complex && t.post) { - const s = >t.val; - return `${s.id} = ${t.type}.${OP_IDS[t.op]}(${emit(s)})`; - } else { - return complex - ? `${t.type}.${OP_IDS[t.op]}1(${emit(t.val)})` - : t.post - ? `(${emit(t.val)}${t.op})` - : `${t.op}${emit(t.val)}`; - } + const complex = isVecOrMat(t) || isInt(t); + const op = t.op; + const val = emit(t.val); + return complex && t.post + ? `${(>t.val).id} = ${t.type}.${OP_IDS[op]}(${val})` + : complex + ? `${t.type}.${OP_IDS[op]}1(${val})` + : t.post + ? `(${val}${op})` + : `${op}${val}`; }, op2: (t) => { const { l, r } = t; - const vec = - isVec(l) || isMat(l) - ? l.type - : isVec(r) || isMat(r) - ? r.type - : undefined; + const vec = isVecOrMat(l) + ? l.type + : isVecOrMat(r) + ? r.type + : undefined; const int = !vec - ? isInt(l) || isUint(l) || isBool(l) + ? isIntOrBool(l) ? l.type - : isInt(r) || isUint(r) || isBool(r) + : isIntOrBool(r) ? r.type : undefined : undefined; @@ -1213,8 +236,8 @@ export const targetJS = () => { swizzle: (t) => t.id.length > 1 - ? `env.swizzle${t.id.length}(${emit(t.val)}, ${$swizzle(t.id)})` - : `${emit(t.val)}[${$swizzle(t.id)}]`, + ? `env.swizzle${t.id.length}(${emit(t.val)}, ${swizzle(t.id)})` + : `${emit(t.val)}[${swizzle(t.id)}]`, sym: (t) => t.id, @@ -1225,21 +248,10 @@ export const targetJS = () => { Object.assign(emit, { compile: (tree, env = JS_DEFAULT_ENV) => { - const exports = - tree.tag === "scope" - ? (tree).body - .filter((x) => x.tag === "fn") - .map( - (f) => - (>f).id + ": " + (>f).id - ) - .join(",\n") - : tree.tag === "fn" - ? `${(>tree).id}: ${(>tree).id}` - : ""; + const exports = buildExports(tree); return new Function( "env", - PRELUDE + emit(tree) + "\nreturn {\n" + exports + "\n};" + [PRELUDE, emit(tree), "return {", exports, "};"].join("\n") )(env); } }); diff --git a/packages/shader-ast-js/test/index.ts b/packages/shader-ast-js/test/index.ts index 1f01c2dccb..c1e8c4ec3d 100644 --- a/packages/shader-ast-js/test/index.ts +++ b/packages/shader-ast-js/test/index.ts @@ -1,6 +1,61 @@ -// import * as assert from "assert"; -// import * as saj from "../src/index"; +import { eqDelta2 } from "@thi.ng/vectors"; +import * as assert from "assert"; +import { JS_DEFAULT_ENV } from "../src/"; describe("shader-ast-js", () => { - it("tests pending"); + it("vec2", () => { + const V2 = JS_DEFAULT_ENV.vec2; + assert.deepEqual(V2.abs([-1, 2]), [1, 2]); + assert.deepEqual(V2.acos([0, 1]), [Math.PI / 2, 0]); + assert.deepEqual(V2.add([1, 2], [10, 20]), [11, 22]); + assert.deepEqual(V2.addnv(10, [1, 2]), [11, 12]); + assert.deepEqual(V2.addvn([1, 2], 10), [11, 12]); + assert.deepEqual(V2.asin([0, 1]), [0, Math.PI / 2]); + assert.deepEqual(V2.atan([0, 1]), [0, 0.7853981633974483]); + assert.deepEqual(V2.atannn([0, 1], [1, 0]), [0, Math.PI / 2]); + assert.deepEqual(V2.ceil([-1.8, 1.2]), [-1, 2]); + assert.deepEqual(V2.cos([0, Math.PI]), [1, -1]); + assert.deepEqual(V2.clamp([-1.8, 1.2], [-1, -1], [1, 1]), [-1, 1]); + assert.deepEqual(V2.dec([-1, 1]), [-2, 0]); + assert.deepEqual(V2.degrees([Math.PI / 2, -Math.PI / 4]), [90, -45]); + assert.deepEqual(V2.distance([10, 20], [11, 19]), Math.SQRT2); + assert.deepEqual(V2.div([1, 2], [10, -10]), [0.1, -0.2], "d1"); + assert.deepEqual(V2.divnv(10, [2, 4]), [5, 2.5], "d2"); + assert.deepEqual(V2.divvn([1, 2], 10), [0.1, 0.2], "d3"); + assert.deepEqual(V2.dot([1, 2], [10, 20]), 50); + assert.deepEqual(V2.exp([2, -3]), [Math.exp(2), Math.exp(-3)]); + assert.deepEqual(V2.exp2([2, -3]), [Math.pow(2, 2), Math.pow(2, -3)]); + // assert.deepEqual(V2.faceForward(), []); + assert.deepEqual(V2.floor([-1.2, 1.2]), [-2, 1], "floor"); + assert.ok(eqDelta2(V2.fract([-1.8, 1.8]), [0.2, 0.8]), "fract"); + assert.ok(eqDelta2(V2.inc([-1.2, 1.2]), [-0.2, 2.2]), "inc"); + assert.deepEqual(V2.inversesqrt([4, 9]), [1 / 2, 1 / 3]); + assert.deepEqual(V2.length([100, 100]), 100 * Math.SQRT2); + assert.deepEqual(V2.log([2, 10]), [Math.LN2, Math.LN10]); + assert.deepEqual(V2.log2([4, 32]), [2, 5]); + //assert.deepEqual(V2.max(), []); + //assert.deepEqual(V2.min(), []); + //assert.deepEqual(V2.mix(), []); + //assert.deepEqual(V2.mixn(), []); + //assert.deepEqual(V2.mod(), []); + //assert.deepEqual(V2.modn(), []); + //assert.deepEqual(V2.mul(), []); + //assert.deepEqual(V2.mulnv(), []); + //assert.deepEqual(V2.mulvn(), []); + //assert.deepEqual(V2.normalize(), []); + //assert.deepEqual(V2.pow(), []); + //assert.deepEqual(V2.radians(), []); + //assert.deepEqual(V2.reflect(), []); + //assert.deepEqual(V2.refract(), []); + //assert.deepEqual(V2.sign(), []); + //assert.deepEqual(V2.sin(), []); + //assert.deepEqual(V2.smoothstep(), []); + //assert.deepEqual(V2.sqrt(), []); + //assert.deepEqual(V2.step(), []); + //assert.deepEqual(V2.sub(), []); + //assert.deepEqual(V2.sub1(), []); + //assert.deepEqual(V2.subnv(), []); + //assert.deepEqual(V2.subvn(), []); + //assert.deepEqual(V2.tan(), []); + }); }); diff --git a/packages/shader-ast-stdlib/CHANGELOG.md b/packages/shader-ast-stdlib/CHANGELOG.md index 86af2651f0..9b856d173e 100644 --- a/packages/shader-ast-stdlib/CHANGELOG.md +++ b/packages/shader-ast-stdlib/CHANGELOG.md @@ -3,6 +3,58 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-stdlib@0.3.0...@thi.ng/shader-ast-stdlib@0.3.1) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/shader-ast-stdlib + + + + + +# [0.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-stdlib@0.2.3...@thi.ng/shader-ast-stdlib@0.3.0) (2019-09-21) + + +### Bug Fixes + +* **shader-ast-stdlib:** fix imports ([188309a](https://github.com/thi-ng/umbrella/commit/188309a)) +* **shader-ast-stdlib:** fix imports ([16823b2](https://github.com/thi-ng/umbrella/commit/16823b2)) + + +### Features + +* **shader-ast-stdlib:** add fragUV() ([b85dc8b](https://github.com/thi-ng/umbrella/commit/b85dc8b)) +* **shader-ast-stdlib:** add guassian blur fns ([759ace7](https://github.com/thi-ng/umbrella/commit/759ace7)) +* **shader-ast-stdlib:** add rotationAroundAxis3/4, matrix conversions ([8a473c1](https://github.com/thi-ng/umbrella/commit/8a473c1)) +* **shader-ast-stdlib:** add snoise3 & curlNoise3 ([a7dc75d](https://github.com/thi-ng/umbrella/commit/a7dc75d)) + + + + + +## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-stdlib@0.2.2...@thi.ng/shader-ast-stdlib@0.2.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/shader-ast-stdlib + + + + + +## [0.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-stdlib@0.2.1...@thi.ng/shader-ast-stdlib@0.2.2) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/shader-ast-stdlib + + + + + +## [0.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-stdlib@0.2.0...@thi.ng/shader-ast-stdlib@0.2.1) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/shader-ast-stdlib + + + + + # [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast-stdlib@0.1.2...@thi.ng/shader-ast-stdlib@0.2.0) (2019-07-31) diff --git a/packages/shader-ast-stdlib/README.md b/packages/shader-ast-stdlib/README.md index 50741f2566..f516046b7f 100644 --- a/packages/shader-ast-stdlib/README.md +++ b/packages/shader-ast-stdlib/README.md @@ -291,7 +291,7 @@ TODO. For now, please see doc strings in source for details... Use the `porterDuff` higher order function to define new blend modes. See -[@thi.ng/porter-duff](https://github.com/thi-ng/umbrella/tree/develop/packages/porter-duff) +[@thi.ng/porter-duff](https://github.com/thi-ng/umbrella/tree/master/packages/porter-duff) for reference. 12 standard PD operators for `vec4` RGBA colors: diff --git a/packages/shader-ast-stdlib/package.json b/packages/shader-ast-stdlib/package.json index 197c9731c7..64821d0858 100644 --- a/packages/shader-ast-stdlib/package.json +++ b/packages/shader-ast-stdlib/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/shader-ast-stdlib", - "version": "0.2.0", + "version": "0.3.1", "description": "Useful functions for GPGPU / shader programming w/ @thi.ng/shader-ast", "module": "./index.js", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "build:test": "rimraf build && tsc -p test/tsconfig.json", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", - "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib color fog lighting math matrix noise raymarch screen sdf tex", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib color fog light math matrix noise raymarch screen sdf tex", "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", "pub": "yarn build:release && yarn publish --access public" }, @@ -29,11 +29,11 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/shader-ast": "^0.2.1" + "@thi.ng/shader-ast": "^0.3.2" }, "keywords": [ "AST", diff --git a/packages/shader-ast-stdlib/src/color/porter-duff.ts b/packages/shader-ast-stdlib/src/color/porter-duff.ts index a53c732ad1..f8b9db7a98 100644 --- a/packages/shader-ast-stdlib/src/color/porter-duff.ts +++ b/packages/shader-ast-stdlib/src/color/porter-duff.ts @@ -8,10 +8,18 @@ import { FloatTerm, mul, ret, - sub + sub, + vec4, + Vec4Sym } from "@thi.ng/shader-ast"; import { clamp01 } from "../math/clamp"; +const coeff = ( + f: Fn2, + a: Vec4Sym, + b: Vec4Sym +) => (f === ZERO ? FLOAT0 : f === ONE ? a : mul(a, f($w(a), $w(b)))); + /** * Higher-order Porter-Duff alpha compositing operator. See * thi.ng/porter-duff for reference. Returns an optimized AST function @@ -23,6 +31,9 @@ import { clamp01 } from "../math/clamp"; * for src/dest colors and are called with the alpha components of both * colors. * + * Optimization only happens for cases where either `fa` and/or `fb` are + * `ZERO`. + * * @param name function name * @param fa src coeff fn * @param fb dest coeff fn @@ -33,19 +44,15 @@ export const porterDuff = ( fb: Fn2 ) => defn("vec4", name, ["vec4", "vec4"], (a, b) => { - const src = - fa === ZERO ? FLOAT0 : fa === ONE ? a : mul(a, fa($w(a), $w(b))); - const dest = - fb === ZERO ? FLOAT0 : fb === ONE ? b : mul(b, fb($w(a), $w(b))); + const src = coeff(fa, a, b); + const dest = coeff(fb, a, b); + const srcZero = src === FLOAT0; + const destZero = dest === FLOAT0; return [ ret( - clamp01( - src === FLOAT0 - ? dest - : dest === FLOAT0 - ? src - : add(src, dest) - ) + srcZero && destZero + ? vec4() + : clamp01(srcZero ? dest : destZero ? src : add(src, dest)) ) ]; }); diff --git a/packages/shader-ast-stdlib/src/index.ts b/packages/shader-ast-stdlib/src/index.ts index ad140b0c7b..03e36bc3a6 100644 --- a/packages/shader-ast-stdlib/src/index.ts +++ b/packages/shader-ast-stdlib/src/index.ts @@ -30,9 +30,11 @@ export * from "./matrix/mvp"; export * from "./matrix/normal"; export * from "./matrix/rotation"; +export * from "./noise/curl3"; export * from "./noise/hash"; export * from "./noise/permute"; export * from "./noise/simplex2"; +export * from "./noise/simplex3"; export * from "./noise/voronoi2"; export * from "./noise/worley2"; @@ -60,6 +62,7 @@ export * from "./sdf/torus"; export * from "./sdf/tri"; export * from "./sdf/union"; +export * from "./tex/blur"; export * from "./tex/index-coord"; export * from "./tex/index-uv"; export * from "./tex/read-index"; diff --git a/packages/shader-ast-stdlib/src/math/clamp.ts b/packages/shader-ast-stdlib/src/math/clamp.ts index 8f088fcc63..7634a2550f 100644 --- a/packages/shader-ast-stdlib/src/math/clamp.ts +++ b/packages/shader-ast-stdlib/src/math/clamp.ts @@ -1,8 +1,6 @@ import { clamp, float, - FLOAT0, - FLOAT1, FloatTerm, Prim, Term, @@ -14,34 +12,29 @@ import { Vec4Term } from "@thi.ng/shader-ast"; -/** - * Inline function, expands to equivalent of `clamp(x, 0, 1)`. - * - * @param x - */ -export const clamp01 = (x: Term): Term => +const __clamp = (min: number, max: number) => ( + x: Term +): Term => >( (x.type === "float" - ? clamp(x, FLOAT0, FLOAT1) + ? clamp(x, float(min), float(max)) : x.type === "vec2" - ? clamp(x, vec2(), vec2(1)) + ? clamp(x, vec2(min), vec2(max)) : x.type === "vec3" - ? clamp(x, vec3(), vec3(1)) - : clamp(x, vec4(), vec4(1))) + ? clamp(x, vec3(min), vec3(max)) + : clamp(x, vec4(min), vec4(max))) ); +/** + * Inline function, expands to equivalent of `clamp(x, 0, 1)`. + * + * @param x + */ +export const clamp01 = __clamp(0, 1); + /** * Inline function, expands to equivalent of `clamp(x, -1, 1)`. * * @param x */ -export const clamp11 = (x: Term): Term => - >( - (x.type === "float" - ? clamp(x, float(-1), FLOAT1) - : x.type === "vec2" - ? clamp(x, vec2(-1), vec2(1)) - : x.type === "vec3" - ? clamp(x, vec3(-1), vec3(1)) - : clamp(x, vec4(-1), vec4(1))) - ); +export const clamp11 = __clamp(-1, 1); diff --git a/packages/shader-ast-stdlib/src/matrix/convert.ts b/packages/shader-ast-stdlib/src/matrix/convert.ts new file mode 100644 index 0000000000..02219c07dd --- /dev/null +++ b/packages/shader-ast-stdlib/src/matrix/convert.ts @@ -0,0 +1,28 @@ +import { + defn, + indexMat, + mat3, + mat4, + ret, + vec3, + vec4 +} from "@thi.ng/shader-ast"; + +export const m22ToM33 = defn("mat3", "m22ToM33", ["mat2"], (m) => { + return [ + ret(mat3(vec3(indexMat(m, 0), 0), vec3(indexMat(m, 1), 0), vec3())) + ]; +}); + +export const m33ToM44 = defn("mat4", "m33ToM44", ["mat3"], (m) => { + return [ + ret( + mat4( + vec4(indexMat(m, 0), 0), + vec4(indexMat(m, 1), 0), + vec4(indexMat(m, 2), 0), + vec4() + ) + ) + ]; +}); diff --git a/packages/shader-ast-stdlib/src/matrix/rotation.ts b/packages/shader-ast-stdlib/src/matrix/rotation.ts index e83f5e6138..087ca491d7 100644 --- a/packages/shader-ast-stdlib/src/matrix/rotation.ts +++ b/packages/shader-ast-stdlib/src/matrix/rotation.ts @@ -1,17 +1,27 @@ import { $x, $y, + $z, + add, + cos, defn, + FloatSym, mat2, mat3, mat4, + mul, neg, + NumericF, ret, + sin, + sub, sym, - Vec2Term + Vec2Term, + vec3 } from "@thi.ng/shader-ast"; import { perpendicularCCW } from "../math/orthogonal"; import { cossin } from "../math/sincos"; +import { m33ToM44 } from "./convert"; export const rotation2 = defn("mat2", "rotation2", ["float"], (theta) => { let cs: Vec2Term; @@ -122,3 +132,42 @@ export const rotationZ4 = defn("mat4", "rotationZ4", ["float"], (theta) => { ) ]; }); + +export const rotationAroundAxis3 = defn( + "mat3", + "rotationAroundAxis3", + ["vec3", "float"], + (axis, theta) => { + let s: FloatSym; + let c: FloatSym; + let t: FloatSym; + const $$ = ( + a: NumericF, + b: NumericF, + c: NumericF, + d: NumericF, + e: NumericF, + f: NumericF, + g: NumericF + ) => add(mul(mul(axis, a), t), mul(vec3(b, c, d), vec3(e, f, g))); + return [ + (s = sym(sin(theta))), + (c = sym(cos(theta))), + (t = sym(sub(1, c))), + ret( + mat3( + $$($x(axis), 1, $z(axis), neg($y(axis)), c, s, s), + $$($y(axis), neg($z(axis)), 1, $x(axis), s, c, s), + $$($z(axis), $y(axis), neg($x(axis)), 1, s, s, c) + ) + ) + ]; + } +); + +export const rotationAroundAxis4 = defn( + "mat4", + "rotationAroundAxis4", + ["vec3", "float"], + (axis, theta) => [ret(m33ToM44(rotationAroundAxis3(axis, theta)))] +); diff --git a/packages/shader-ast-stdlib/src/noise/curl3.ts b/packages/shader-ast-stdlib/src/noise/curl3.ts new file mode 100644 index 0000000000..dbf94087bc --- /dev/null +++ b/packages/shader-ast-stdlib/src/noise/curl3.ts @@ -0,0 +1,52 @@ +import { + $, + $x, + $y, + $z, + add, + defn, + div, + mul, + ret, + sub, + sym, + vec2, + Vec2Sym, + vec3, + Vec3Sym +} from "@thi.ng/shader-ast"; +import { snoiseVec3 } from "./simplex3"; + +export const curlNoise3 = defn( + "vec3", + "curlNoise3", + ["vec3", "float"], + (p, e) => { + let D: Vec2Sym; + let px0: Vec3Sym; + let px1: Vec3Sym; + let py0: Vec3Sym; + let py1: Vec3Sym; + let pz0: Vec3Sym; + let pz1: Vec3Sym; + return [ + (D = sym(vec2(e, 0))), + (px0 = sym(snoiseVec3(sub(p, $(D, "xyy"))))), + (px1 = sym(snoiseVec3(add(p, $(D, "xyy"))))), + (py0 = sym(snoiseVec3(sub(p, $(D, "yxy"))))), + (py1 = sym(snoiseVec3(add(p, $(D, "yxy"))))), + (pz0 = sym(snoiseVec3(sub(p, $(D, "yyx"))))), + (pz1 = sym(snoiseVec3(add(p, $(D, "yyx"))))), + ret( + div( + vec3( + add(sub(sub($z(py1), $z(py0)), $y(pz1)), $y(pz0)), + add(sub(sub($x(pz1), $x(pz0)), $z(px1)), $z(px0)), + add(sub(sub($y(px1), $y(px0)), $x(py1)), $x(py0)) + ), + mul(2, e) + ) + ) + ]; + } +); diff --git a/packages/shader-ast-stdlib/src/noise/permute.ts b/packages/shader-ast-stdlib/src/noise/permute.ts index 11c426d164..e4a6ff61b3 100644 --- a/packages/shader-ast-stdlib/src/noise/permute.ts +++ b/packages/shader-ast-stdlib/src/noise/permute.ts @@ -5,21 +5,19 @@ import { FLOAT1, mod, mul, + Prim, ret } from "@thi.ng/shader-ast"; -export const permute = defn("float", "permute", ["float"], (v) => [ - ret(mod(mul(v, add(mul(v, float(34)), FLOAT1)), float(289))) -]); +const __permute = (type: T, suffix = "") => + defn(type, `permute${suffix}`, [type], (v) => [ + ret(mod(mul(v, add(mul(v, float(34)), FLOAT1)), float(289))) + ]); -export const permute2 = defn("vec2", "permute2", ["vec2"], (v) => [ - ret(mod(mul(v, add(mul(v, float(34)), FLOAT1)), float(289))) -]); +export const permute = __permute("float"); -export const permute3 = defn("vec3", "permute3", ["vec3"], (v) => [ - ret(mod(mul(v, add(mul(v, float(34)), FLOAT1)), float(289))) -]); +export const permute2 = __permute("vec2", "2"); -export const permute4 = defn("vec4", "permute4", ["vec4"], (v) => [ - ret(mod(mul(v, add(mul(v, float(34)), FLOAT1)), float(289))) -]); +export const permute3 = __permute("vec3", "3"); + +export const permute4 = __permute("vec4", "4"); diff --git a/packages/shader-ast-stdlib/src/noise/simplex3.ts b/packages/shader-ast-stdlib/src/noise/simplex3.ts new file mode 100644 index 0000000000..5fd0f2a114 --- /dev/null +++ b/packages/shader-ast-stdlib/src/noise/simplex3.ts @@ -0,0 +1,170 @@ +import { + $, + $w, + $x, + $xy, + $y, + $z, + abs, + add, + assign, + defn, + dot, + float, + FLOAT0, + FLOAT05, + FLOAT1, + FLOAT2, + floor, + max, + min, + mod, + mul, + neg, + ret, + step, + sub, + sym, + vec3, + Vec3Sym, + vec4, + Vec4Sym +} from "@thi.ng/shader-ast"; +import { permute4 } from "./permute"; + +export const snoise3 = defn("float", "snoise3", ["vec3"], (v) => { + let g: Vec3Sym; + let j: Vec4Sym; + let l: Vec3Sym; + let m: Vec4Sym; + let p: Vec4Sym; + let norm: Vec4Sym; + let _x: Vec4Sym; + let x: Vec4Sym; + let y: Vec4Sym; + let h: Vec4Sym; + let sh: Vec4Sym; + let a0: Vec4Sym; + let a1: Vec4Sym; + let b0: Vec4Sym; + let b1: Vec4Sym; + let x0: Vec3Sym; + let x1: Vec3Sym; + let x2: Vec3Sym; + let x3: Vec3Sym; + let p0: Vec3Sym; + let p1: Vec3Sym; + let p2: Vec3Sym; + let p3: Vec3Sym; + let i: Vec3Sym; + let i1: Vec3Sym; + let i2: Vec3Sym; + const NS = 1 / 7; + const NSX = NS * 2; + const NSY = NS * 0.5 - 1; + return [ + (i = sym(floor(add(v, dot(v, vec3(1 / 3)))))), + (x0 = sym(add(sub(v, i), dot(i, vec3(1 / 6))))), + (g = sym(step($(x0, "yzx"), x0))), + (l = sym($(sub(FLOAT1, g), "zxy"))), + (i1 = sym(min(g, l))), + (i2 = sym(max(g, l))), + (x1 = sym(add(sub(x0, i1), 1 / 6))), + (x2 = sym(add(sub(x0, i2), 1 / 3))), + (x3 = sym(sub(x0, FLOAT05))), + assign(i, mod(i, float(289))), + (p = sym( + permute4( + add( + permute4( + add( + permute4( + add(vec4(FLOAT0, $z(i1), $z(i2), FLOAT1), $z(i)) + ), + add(vec4(FLOAT0, $y(i1), $y(i2), FLOAT1), $y(i)) + ) + ), + add(vec4(FLOAT0, $x(i1), $x(i2), FLOAT1), $x(i)) + ) + ) + )), + (j = sym(sub(p, mul(floor(mul(p, NS * NS)), 49)))), + (_x = sym(floor(mul(j, NS)))), + (x = sym(add(mul(_x, NSX), NSY))), + (y = sym(add(mul(floor(sub(j, mul(_x, 7))), NSX), NSY))), + (h = sym(sub(sub(FLOAT1, abs(x)), abs(y)))), + (sh = sym(neg(step(h, vec4())))), + (b0 = sym(vec4($xy(x), $xy(y)))), + (b1 = sym(vec4($(x, "zw"), $(y, "zw")))), + (a0 = sym( + add( + $(b0, "xzyw"), + mul( + $(add(mul(floor(b0), FLOAT2), FLOAT1), "xzyw"), + $(sh, "xxyy") + ) + ) + )), + (a1 = sym( + add( + $(b1, "xzyw"), + mul( + $(add(mul(floor(b1), FLOAT2), FLOAT1), "xzyw"), + $(sh, "zzww") + ) + ) + )), + (p0 = sym(vec3($xy(a0), $x(h)))), + (p1 = sym(vec3($(a0, "zw"), $y(h)))), + (p2 = sym(vec3($xy(a1), $z(h)))), + (p3 = sym(vec3($(a1, "zw"), $w(h)))), + (norm = sym( + sub( + 1.79284291400159, + mul( + vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)), + 0.85373472095314 + ) + ) + )), + assign(p0, mul(p0, $x(norm))), + assign(p1, mul(p1, $y(norm))), + assign(p2, mul(p2, $z(norm))), + assign(p3, mul(p3, $w(norm))), + (m = sym( + max( + vec4(), + sub( + 0.6, + vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)) + ) + ) + )), + assign(m, mul(m, m)), + ret( + mul( + 42, + dot( + mul(m, m), + vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)) + ) + ) + ) + ]; +}); + +export const snoiseVec3 = defn("vec3", "snoiseVec3", ["vec3"], (p) => { + return [ + ret( + vec3( + snoise3(p), + snoise3( + vec3(sub($y(p), 19.1), add($z(p), 33.4), add($x(p), 47.2)) + ), + snoise3( + vec3(add($z(p), 74.2), sub($x(p), 124.5), add($y(p), 99.4)) + ) + ) + ) + ]; +}); diff --git a/packages/shader-ast-stdlib/src/screen/uv.ts b/packages/shader-ast-stdlib/src/screen/uv.ts index 5b119ed8f0..924b96235a 100644 --- a/packages/shader-ast-stdlib/src/screen/uv.ts +++ b/packages/shader-ast-stdlib/src/screen/uv.ts @@ -1,5 +1,6 @@ import { $x, + $xy, $y, assign, defn, @@ -7,10 +8,21 @@ import { mul, ret, sym, - Vec2Sym + Vec2Sym, + Vec2Term, + Vec4Term } from "@thi.ng/shader-ast"; import { fit0111 } from "../math/fit"; +/** + * Computes UV coord in [0..1] interval from given `fragCoord` and screen `res`. + * + * @param fragCoord + * @param res + */ +export const fragUV = (fragCoord: Vec4Term, res: Vec2Term) => + div($xy(fragCoord), res); + /** * Takes `pos`, a screen coord (e.g. gl_FragCoord) and viewport * resolution `res`, returns aspect corrected uv, with uv.y in [-1..1] diff --git a/packages/shader-ast-stdlib/src/tex/blur.ts b/packages/shader-ast-stdlib/src/tex/blur.ts new file mode 100644 index 0000000000..8a0faa3217 --- /dev/null +++ b/packages/shader-ast-stdlib/src/tex/blur.ts @@ -0,0 +1,137 @@ +import { + add, + assign, + defn, + div, + mul, + ret, + Sampler2DSym, + sub, + sym, + texture, + vec2, + Vec2Sym, + Vec4Sym +} from "@thi.ng/shader-ast"; + +/** + * Inline function. Computes single blur step for given +/- offset & + * weight. + * + * @param col + * @param tex + * @param uv + * @param off + * @param k + */ +const singlePass = ( + col: Vec4Sym, + tex: Sampler2DSym, + uv: Vec2Sym, + off: Vec2Sym, + k: number +) => + assign( + col, + add( + col, + mul(add(texture(tex, add(uv, off)), texture(tex, sub(uv, off))), k) + ) + ); + +/** + * 5x5 gaussian blur texture lookup, optimized, but based on: + * + * - http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/ + * - https://github.com/Jam3/glsl-fast-gaussian-blur + * + * @param tex sampler2D + * @param res resolution + * @param uv + * @param dir blur pass direction (`vec2(1,0)` or `vec2(0,1)`) + */ +export const blur5 = defn( + "vec4", + "blur5", + ["sampler2D", "vec2", "vec2", "vec2"], + (tex, res, uv, dir) => { + let col: Vec4Sym; + let off: Vec2Sym; + const k1 = 0.29411764705882354; + const k2 = 0.35294117647058826; + return [ + (off = sym(div(mul(vec2(1 + 1 / 3), dir), res))), + (col = sym(mul(texture(tex, uv), k1))), + singlePass(col, tex, uv, off, k2), + ret(col) + ]; + } +); + +/** + * 9x9 gaussian blur texture lookup, optimized, but based on: + * + * - http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/ + * - https://github.com/Jam3/glsl-fast-gaussian-blur + * + * @param tex sampler2D + * @param res resolution + * @param uv + * @param dir blur pass direction (`vec2(1,0)` or `vec2(0,1)`) + */ +export const blur9 = defn( + "vec4", + "blur9", + ["sampler2D", "vec2", "vec2", "vec2"], + (tex, res, uv, dir) => { + let col: Vec4Sym; + let off: Vec2Sym; + let off2: Vec2Sym; + const k1 = 0.3162162162; + const k2 = 0.0702702703; + return [ + (off = sym(div(mul(vec2(1.3846153846), dir), res))), + (off2 = sym(div(mul(vec2(3.2307692308), dir), res))), + (col = sym(mul(texture(tex, uv), 0.227027027))), + singlePass(col, tex, uv, off, k1), + singlePass(col, tex, uv, off2, k2), + ret(col) + ]; + } +); + +/** + * 13x13 gaussian blur texture lookup, optimized, but based on: + * + * - http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/ + * - https://github.com/Jam3/glsl-fast-gaussian-blur + * + * @param tex sampler2D + * @param res resolution + * @param uv + * @param dir blur pass direction (`vec2(1,0)` or `vec2(0,1)`) + */ +export const blur13 = defn( + "vec4", + "blur13", + ["sampler2D", "vec2", "vec2", "vec2"], + (tex, res, uv, dir) => { + let col: Vec4Sym; + let off: Vec2Sym; + let off2: Vec2Sym; + let off3: Vec2Sym; + const k1 = 0.2969069646728344; + const k2 = 0.09447039785044732; + const k3 = 0.010381362401148057; + return [ + (off = sym(div(mul(vec2(1.411764705882353), dir), res))), + (off2 = sym(div(mul(vec2(3.2941176470588234), dir), res))), + (off3 = sym(div(mul(vec2(5.176470588235294), dir), res))), + (col = sym(mul(texture(tex, uv), 0.1964825501511404))), + singlePass(col, tex, uv, off, k1), + singlePass(col, tex, uv, off2, k2), + singlePass(col, tex, uv, off3, k3), + ret(col) + ]; + } +); diff --git a/packages/shader-ast-stdlib/src/tex/read-index.ts b/packages/shader-ast-stdlib/src/tex/read-index.ts index 698dc7099e..9d0b72f18e 100644 --- a/packages/shader-ast-stdlib/src/tex/read-index.ts +++ b/packages/shader-ast-stdlib/src/tex/read-index.ts @@ -17,7 +17,7 @@ import { indexToUV } from "./index-uv"; * @param size */ export const readIndex1 = (tex: Sampler2DTerm, i: IntTerm, size: IVec2Term) => - $x(texture(tex, indexToUV(i, size))); + $x(readIndex4(tex, i, size)); /** * Inline function. Returns vec2 (x,y components) at index `i` in `tex`. @@ -27,7 +27,7 @@ export const readIndex1 = (tex: Sampler2DTerm, i: IntTerm, size: IVec2Term) => * @param size */ export const readIndex2 = (tex: Sampler2DTerm, i: IntTerm, size: IVec2Term) => - $xy(texture(tex, indexToUV(i, size))); + $xy(readIndex4(tex, i, size)); /** * Inline function. Returns vec3 (x,y,z components) at index `i` in `tex`. @@ -37,7 +37,7 @@ export const readIndex2 = (tex: Sampler2DTerm, i: IntTerm, size: IVec2Term) => * @param size */ export const readIndex3 = (tex: Sampler2DTerm, i: IntTerm, size: IVec2Term) => - $xyz(texture(tex, indexToUV(i, size))); + $xyz(readIndex4(tex, i, size)); /** * Inline function. Returns vec4 at index `i` in `tex`. diff --git a/packages/shader-ast/CHANGELOG.md b/packages/shader-ast/CHANGELOG.md index 12581c9fa6..3ebc9c0cc8 100644 --- a/packages/shader-ast/CHANGELOG.md +++ b/packages/shader-ast/CHANGELOG.md @@ -3,6 +3,52 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast@0.3.1...@thi.ng/shader-ast@0.3.2) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/shader-ast + + + + + +## [0.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast@0.3.0...@thi.ng/shader-ast@0.3.1) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/shader-ast + + + + + +# [0.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast@0.2.3...@thi.ng/shader-ast@0.3.0) (2019-08-21) + + +### Features + +* **shader-ast:** add modf(), isnan(), isinf() built-ins ([7fae67b](https://github.com/thi-ng/umbrella/commit/7fae67b)) + + + + + +## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast@0.2.2...@thi.ng/shader-ast@0.2.3) (2019-08-17) + + +### Bug Fixes + +* **shader-ast:** update atan built-in handling ([9f0c739](https://github.com/thi-ng/umbrella/commit/9f0c739)) + + + + + +## [0.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast@0.2.1...@thi.ng/shader-ast@0.2.2) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/shader-ast + + + + + ## [0.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/shader-ast@0.2.0...@thi.ng/shader-ast@0.2.1) (2019-07-31) **Note:** Version bump only for package @thi.ng/shader-ast diff --git a/packages/shader-ast/README.md b/packages/shader-ast/README.md index 978f7fe063..9b5660897b 100644 --- a/packages/shader-ast/README.md +++ b/packages/shader-ast/README.md @@ -37,7 +37,7 @@ This project is part of the ## About -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/shader-ast-01.jpg) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/shader-ast/shader-ast-01.jpg) Example shader running in plain JS & Canvas 2D context, cross-compiled JS/GLSL outputs shown on the right @@ -62,13 +62,13 @@ VEX](http://www.sidefx.com/docs/houdini/vex/index.html) (in-progress), [WASM](https://webassembly.org), [WHLSL for WebGPU](https://github.com/gpuweb/WHLSL) in the near future as well. -![webgl/canvas2d comparison](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/shader-ast-raymarch-compare.jpg) +![webgl/canvas2d comparison](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/shader-ast/shader-ast-raymarch-compare.jpg) Comparison of the raymarch shader example (link further below), cross compiled to both GLSL/WebGL and JavaScript w/ Canvas2D API and showing the difference image of both results. -![VEX plane displacement](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/shader-ast-raymarch-vex-sm.gif) +![VEX plane displacement](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/shader-ast/shader-ast-raymarch-vex-sm.gif) The same raymarching example compiled to Houdini VEX and used as "Point Wrangle" to displace a grid geometry (using only the depth value of the @@ -326,7 +326,7 @@ Swizzle patterns are type checked in the editor (and at compile time), i.e. ### Built-in functions The most common set of GLSL ES 3.0 builtins are supported. See -[builtins.ts](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast/src/builtins.ts) +[/builtin](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast/src/builtin/) for reference. ### User defined functions @@ -402,7 +402,7 @@ instantiated, typed symbols representing each arg and can use any name within that function (also as shown in the above example). See `SymOpts` interface in -[api.ts](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast/src/api.ts) +[/api/syms.ts](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast/src/api/syms.ts) for more details about the options object... #### Inline functions diff --git a/packages/shader-ast/package.json b/packages/shader-ast/package.json index e4d5e3b82e..7f7ea1f499 100644 --- a/packages/shader-ast/package.json +++ b/packages/shader-ast/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/shader-ast", - "version": "0.2.1", + "version": "0.3.2", "description": "DSL to define shader code in TypeScript and cross-compile to GLSL, JS and other targets", "module": "./index.js", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "build:test": "rimraf build && tsc -p test/tsconfig.json", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", - "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib std", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib api ast builtin", "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", "pub": "yarn build:release && yarn publish --access public" }, @@ -29,15 +29,15 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/defmulti": "^1.1.2", - "@thi.ng/dgraph": "^1.1.12", - "@thi.ng/errors": "^1.1.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/defmulti": "^1.2.0", + "@thi.ng/dgraph": "^1.1.16", + "@thi.ng/errors": "^1.2.1" }, "keywords": [ "AST", diff --git a/packages/shader-ast/src/api.ts b/packages/shader-ast/src/api.ts deleted file mode 100644 index a6e5a13e25..0000000000 --- a/packages/shader-ast/src/api.ts +++ /dev/null @@ -1,736 +0,0 @@ -import { - Fn, - Fn0, - Fn2, - Fn3, - Fn4, - Fn5, - Fn6, - Fn7, - Fn8 -} from "@thi.ng/api"; - -export type Tag = - | "arg" - | "array_init" - | "assign" - | "call" - | "call_i" - | "ctrl" - | "decl" - | "fn" - | "for" - | "idx" - | "if" - | "lit" - | "op1" - | "op2" - | "ret" - | "scope" - | "swizzle" - | "sym" - | "ternary" - | "while"; - -export type Type = - | "void" - | "bool" - | "bool[]" - | "float" - | "float[]" - | "int" - | "int[]" - | "uint" - | "uint[]" - | "vec2" - | "vec2[]" - | "vec3" - | "vec3[]" - | "vec4" - | "vec4[]" - | "ivec2" - | "ivec2[]" - | "ivec3" - | "ivec3[]" - | "ivec4" - | "ivec4[]" - | "uvec2" - | "uvec2[]" - | "uvec3" - | "uvec3[]" - | "uvec4" - | "uvec4[]" - | "bvec2" - | "bvec2[]" - | "bvec3" - | "bvec3[]" - | "bvec4" - | "bvec4[]" - | "mat2" - | "mat2[]" - | "mat3" - | "mat3[]" - | "mat4" - | "mat4[]" - | "sampler2D" - | "sampler2D[]" - | "sampler3D" - | "sampler3D[]" - | "samplerCube" - | "samplerCube[]" - | "sampler2DShadow" - | "sampler2DShadow[]" - | "samplerCubeShadow" - | "samplerCubeShadow[]" - | "isampler2D" - | "isampler2D[]" - | "isampler3D" - | "isampler3D[]" - | "isamplerCube" - | "isamplerCube[]" - | "usampler2D" - | "usampler2D[]" - | "usampler3D" - | "usampler3D[]" - | "usamplerCube" - | "usamplerCube[]"; - -export interface ArrayTypeMap { - bool: "bool[]"; - float: "float[]"; - int: "int[]"; - uint: "uint[]"; - vec2: "vec2[]"; - vec3: "vec3[]"; - vec4: "vec4[]"; - ivec2: "ivec2[]"; - ivec3: "ivec3[]"; - ivec4: "ivec4[]"; - uvec2: "uvec2[]"; - uvec3: "uvec3[]"; - uvec4: "uvec4[]"; - bvec2: "bvec2[]"; - bvec3: "bvec3[]"; - bvec4: "bvec4[]"; - mat2: "mat2[]"; - mat3: "mat3[]"; - mat4: "mat4[]"; - sampler2D: "sampler2D[]"; - sampler3D: "sampler3D[]"; - samplerCube: "samplerCube[]"; - sampler2DShadow: "sampler2DShadow[]"; - samplerCubeShadow: "samplerCubeShadow[]"; - isampler2D: "isampler2D[]"; - isampler3D: "isampler3D[]"; - isamplerCube: "isamplerCube[]"; - usampler2D: "usampler2D[]"; - usampler3D: "usampler3D[]"; - usamplerCube: "usamplerCube[]"; -} - -export interface IndexTypeMap { - "bool[]": "bool"; - "float[]": "float"; - "int[]": "int"; - "uint[]": "uint"; - "vec2[]": "vec2"; - "vec3[]": "vec3"; - "vec4[]": "vec4"; - "ivec2[]": "ivec2"; - "ivec3[]": "ivec3"; - "ivec4[]": "ivec4"; - "uvec2[]": "uvec2"; - "uvec3[]": "uvec3"; - "uvec4[]": "uvec4"; - "bvec2[]": "bvec2"; - "bvec3[]": "bvec3"; - "bvec4[]": "bvec4"; -} - -export type BoolTerm = Term<"bool">; -export type FloatTerm = Term<"float">; -export type IntTerm = Term<"int">; -export type UintTerm = Term<"uint">; -export type Vec2Term = Term<"vec2">; -export type Vec3Term = Term<"vec3">; -export type Vec4Term = Term<"vec4">; -export type IVec2Term = Term<"ivec2">; -export type IVec3Term = Term<"ivec3">; -export type IVec4Term = Term<"ivec4">; -export type UVec2Term = Term<"uvec2">; -export type UVec3Term = Term<"uvec3">; -export type UVec4Term = Term<"uvec4">; -export type BVec2Term = Term<"bvec2">; -export type BVec3Term = Term<"bvec3">; -export type BVec4Term = Term<"bvec4">; -export type Mat2Term = Term<"mat2">; -export type Mat3Term = Term<"mat3">; -export type Mat4Term = Term<"mat4">; -export type Sampler2DTerm = Term<"sampler2D">; -export type Sampler3DTerm = Term<"sampler3D">; -export type SamplerCubeTerm = Term<"samplerCube">; -export type ISampler2DTerm = Term<"isampler2D">; -export type ISampler3DTerm = Term<"isampler3D">; -export type ISamplerCubeTerm = Term<"isamplerCube">; -export type USampler2DTerm = Term<"usampler2D">; -export type USampler3DTerm = Term<"usampler3D">; -export type USamplerCubeTerm = Term<"usamplerCube">; - -export type BoolSym = Sym<"bool">; -export type FloatSym = Sym<"float">; -export type IntSym = Sym<"int">; -export type UintSym = Sym<"uint">; -export type Vec2Sym = Sym<"vec2">; -export type Vec3Sym = Sym<"vec3">; -export type Vec4Sym = Sym<"vec4">; -export type IVec2Sym = Sym<"ivec2">; -export type IVec3Sym = Sym<"ivec3">; -export type IVec4Sym = Sym<"ivec4">; -export type UVec2Sym = Sym<"uvec2">; -export type UVec3Sym = Sym<"uvec3">; -export type UVec4Sym = Sym<"uvec4">; -export type BVec2Sym = Sym<"bvec2">; -export type BVec3Sym = Sym<"bvec3">; -export type BVec4Sym = Sym<"bvec4">; -export type Mat2Sym = Sym<"mat2">; -export type Mat3Sym = Sym<"mat3">; -export type Mat4Sym = Sym<"mat4">; -export type Sampler2DSym = Sym<"sampler2D">; -export type Sampler3DSym = Sym<"sampler3D">; -export type SamplerCubeSym = Sym<"samplerCube">; -export type ISampler2DSym = Sym<"isampler2D">; -export type ISampler3DSym = Sym<"isampler3D">; -export type ISamplerCubeSym = Sym<"isamplerCube">; -export type USampler2DSym = Sym<"usampler2D">; -export type USampler3DSym = Sym<"usampler3D">; -export type USamplerCubeSym = Sym<"usamplerCube">; - -export interface MatIndexTypeMap { - mat2: "vec2"; - mat3: "vec3"; - mat4: "vec4"; -} - -export type Indexable = keyof IndexTypeMap; - -export type Vec = "vec2" | "vec3" | "vec4"; -export type IVec = "ivec2" | "ivec3" | "ivec4"; -export type UVec = "uvec2" | "uvec3" | "uvec4"; -export type BVec = "bvec2" | "bvec3" | "bvec4"; -export type Mat = "mat2" | "mat3" | "mat4"; -export type Sampler = - | "sampler2D" - | "sampler3D" - | "samplerCube" - | "sampler2DShadow" - | "samplerCubeShadow" - | "isampler2D" - | "isampler3D" - | "isamplerCube" - | "usampler2D" - | "usampler3D" - | "usamplerCube"; - -export type Prim = "float" | Vec; -export type Int = "int" | "uint"; -export type Comparable = "float" | Int; -export type Numeric = number | FloatTerm | IntTerm | UintTerm; -export type NumericF = number | FloatTerm; -export type NumericI = number | IntTerm; -export type NumericU = number | UintTerm; -export type NumericB = boolean | Numeric | BoolTerm; - -export type Assignable = Sym | Swizzle | Index; - -export type MathOperator = "+" | "-" | "*" | "/" | "%" | "++" | "--"; -export type LogicOperator = "!" | "||" | "&&"; -export type ComparisonOperator = "<" | "<=" | "==" | "!=" | ">=" | ">"; -export type BitOperator = "<<" | ">>" | "|" | "&" | "^" | "~"; -export type Operator = - | MathOperator - | LogicOperator - | ComparisonOperator - | BitOperator; - -// swizzle gen: -// console.log([...permutations("xyz","xyz")].map((x) =>`"${x.join("")}"`).join(" | ")) - -export type Swizzle2_1 = "x" | "y"; -export type Swizzle2_2 = "xx" | "xy" | "yx" | "yy"; -// prettier-ignore -export type Swizzle2_3 = "xxx" | "xxy" | "xyx" | "xyy" | "yxx" | "yxy" | "yyx" | "yyy"; -// prettier-ignore -export type Swizzle2_4 = "xxxx" | "xxxy" | "xxyx" | "xxyy" | "xyxx" | "xyxy" | "xyyx" | "xyyy" | "yxxx" | "yxxy" | "yxyx" | "yxyy" | "yyxx" | "yyxy" | "yyyx" | "yyyy"; - -export type Swizzle3_1 = "x" | "y" | "z"; -// prettier-ignore -export type Swizzle3_2 = "xx" | "xy" | "xz" | "yx" | "yy" | "yz" | "zx" | "zy" | "zz"; -// prettier-ignore -export type Swizzle3_3 = "xxx" | "xxy" | "xxz" | "xyx" | "xyy" | "xyz" | "xzx" | "xzy" | "xzz" | "yxx" | "yxy" | "yxz" | "yyx" | "yyy" | "yyz" | "yzx" | "yzy" | "yzz" | "zxx" | "zxy" | "zxz" | "zyx" | "zyy" | "zyz" | "zzx" | "zzy" | "zzz"; -// prettier-ignore -export type Swizzle3_4 = "xxxx" | "xxxy" | "xxxz" | "xxyx" | "xxyy" | "xxyz" | "xxzx" | "xxzy" | "xxzz" | "xyxx" | "xyxy" | "xyxz" | "xyyx" | "xyyy" | "xyyz" | "xyzx" | "xyzy" | "xyzz" | "xzxx" | "xzxy" | "xzxz" | "xzyx" | "xzyy" | "xzyz" | "xzzx" | "xzzy" | "xzzz" | "yxxx" | "yxxy" | "yxxz" | "yxyx" | "yxyy" | "yxyz" | "yxzx" | "yxzy" | "yxzz" | "yyxx" | "yyxy" | "yyxz" | "yyyx" | "yyyy" | "yyyz" | "yyzx" | "yyzy" | "yyzz" | "yzxx" | "yzxy" | "yzxz" | "yzyx" | "yzyy" | "yzyz" | "yzzx" | "yzzy" | "yzzz" | "zxxx" | "zxxy" | "zxxz" | "zxyx" | "zxyy" | "zxyz" | "zxzx" | "zxzy" | "zxzz" | "zyxx" | "zyxy" | "zyxz" | "zyyx" | "zyyy" | "zyyz" | "zyzx" | "zyzy" | "zyzz" | "zzxx" | "zzxy" | "zzxz" | "zzyx" | "zzyy" | "zzyz" | "zzzx" | "zzzy" | "zzzz"; - -export type Swizzle4_1 = "x" | "y" | "z" | "w"; -// prettier-ignore -export type Swizzle4_2 = "xx" | "xy" | "xz" | "xw" | "yx" | "yy" | "yz" | "yw" | "zx" | "zy" | "zz" | "zw" | "wx" | "wy" | "wz" | "ww"; -// prettier-ignore -export type Swizzle4_3 = "xxx" | "xxy" | "xxz" | "xxw" | "xyx" | "xyy" | "xyz" | "xyw" | "xzx" | "xzy" | "xzz" | "xzw" | "xwx" | "xwy" | "xwz" | "xww" | "yxx" | "yxy" | "yxz" | "yxw" | "yyx" | "yyy" | "yyz" | "yyw" | "yzx" | "yzy" | "yzz" | "yzw" | "ywx" | "ywy" | "ywz" | "yww" | "zxx" | "zxy" | "zxz" | "zxw" | "zyx" | "zyy" | "zyz" | "zyw" | "zzx" | "zzy" | "zzz" | "zzw" | "zwx" | "zwy" | "zwz" | "zww" | "wxx" | "wxy" | "wxz" | "wxw" | "wyx" | "wyy" | "wyz" | "wyw" | "wzx" | "wzy" | "wzz" | "wzw" | "wwx" | "wwy" | "wwz" | "www"; -// prettier-ignore -export type Swizzle4_4 = "xxxx" | "xxxy" | "xxxz" | "xxxw" | "xxyx" | "xxyy" | "xxyz" | "xxyw" | "xxzx" | "xxzy" | "xxzz" | "xxzw" | "xxwx" | "xxwy" | "xxwz" | "xxww" | "xyxx" | "xyxy" | "xyxz" | "xyxw" | "xyyx" | "xyyy" | "xyyz" | "xyyw" | "xyzx" | "xyzy" | "xyzz" | "xyzw" | "xywx" | "xywy" | "xywz" | "xyww" | "xzxx" | "xzxy" | "xzxz" | "xzxw" | "xzyx" | "xzyy" | "xzyz" | "xzyw" | "xzzx" | "xzzy" | "xzzz" | "xzzw" | "xzwx" | "xzwy" | "xzwz" | "xzww" | "xwxx" | "xwxy" | "xwxz" | "xwxw" | "xwyx" | "xwyy" | "xwyz" | "xwyw" | "xwzx" | "xwzy" | "xwzz" | "xwzw" | "xwwx" | "xwwy" | "xwwz" | "xwww" | "yxxx" | "yxxy" | "yxxz" | "yxxw" | "yxyx" | "yxyy" | "yxyz" | "yxyw" | "yxzx" | "yxzy" | "yxzz" | "yxzw" | "yxwx" | "yxwy" | "yxwz" | "yxww" | "yyxx" | "yyxy" | "yyxz" | "yyxw" | "yyyx" | "yyyy" | "yyyz" | "yyyw" | "yyzx" | "yyzy" | "yyzz" | "yyzw" | "yywx" | "yywy" | "yywz" | "yyww" | "yzxx" | "yzxy" | "yzxz" | "yzxw" | "yzyx" | "yzyy" | "yzyz" | "yzyw" | "yzzx" | "yzzy" | "yzzz" | "yzzw" | "yzwx" | "yzwy" | "yzwz" | "yzww" | "ywxx" | "ywxy" | "ywxz" | "ywxw" | "ywyx" | "ywyy" | "ywyz" | "ywyw" | "ywzx" | "ywzy" | "ywzz" | "ywzw" | "ywwx" | "ywwy" | "ywwz" | "ywww" | "zxxx" | "zxxy" | "zxxz" | "zxxw" | "zxyx" | "zxyy" | "zxyz" | "zxyw" | "zxzx" | "zxzy" | "zxzz" | "zxzw" | "zxwx" | "zxwy" | "zxwz" | "zxww" | "zyxx" | "zyxy" | "zyxz" | "zyxw" | "zyyx" | "zyyy" | "zyyz" | "zyyw" | "zyzx" | "zyzy" | "zyzz" | "zyzw" | "zywx" | "zywy" | "zywz" | "zyww" | "zzxx" | "zzxy" | "zzxz" | "zzxw" | "zzyx" | "zzyy" | "zzyz" | "zzyw" | "zzzx" | "zzzy" | "zzzz" | "zzzw" | "zzwx" | "zzwy" | "zzwz" | "zzww" | "zwxx" | "zwxy" | "zwxz" | "zwxw" | "zwyx" | "zwyy" | "zwyz" | "zwyw" | "zwzx" | "zwzy" | "zwzz" | "zwzw" | "zwwx" | "zwwy" | "zwwz" | "zwww" | "wxxx" | "wxxy" | "wxxz" | "wxxw" | "wxyx" | "wxyy" | "wxyz" | "wxyw" | "wxzx" | "wxzy" | "wxzz" | "wxzw" | "wxwx" | "wxwy" | "wxwz" | "wxww" | "wyxx" | "wyxy" | "wyxz" | "wyxw" | "wyyx" | "wyyy" | "wyyz" | "wyyw" | "wyzx" | "wyzy" | "wyzz" | "wyzw" | "wywx" | "wywy" | "wywz" | "wyww" | "wzxx" | "wzxy" | "wzxz" | "wzxw" | "wzyx" | "wzyy" | "wzyz" | "wzyw" | "wzzx" | "wzzy" | "wzzz" | "wzzw" | "wzwx" | "wzwy" | "wzwz" | "wzww" | "wwxx" | "wwxy" | "wwxz" | "wwxw" | "wwyx" | "wwyy" | "wwyz" | "wwyw" | "wwzx" | "wwzy" | "wwzz" | "wwzw" | "wwwx" | "wwwy" | "wwwz" | "wwww"; - -export type Swizzle2 = Swizzle2_1 | Swizzle2_2 | Swizzle2_3 | Swizzle2_4; -export type Swizzle3 = Swizzle3_1 | Swizzle3_2 | Swizzle3_3 | Swizzle3_4; -export type Swizzle4 = Swizzle4_1 | Swizzle4_2 | Swizzle4_3 | Swizzle4_4; - -export type Arg = A | [A, string?, SymOpts?]; -export type Arg1 = [Arg]; -export type Arg2 = [Arg, Arg]; -export type Arg3 = [ - Arg, - Arg, - Arg -]; -export type Arg4< - A extends Type, - B extends Type, - C extends Type, - D extends Type -> = [Arg, Arg, Arg, Arg]; -export type Arg5< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type -> = [Arg, Arg, Arg, Arg, Arg]; -export type Arg6< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type -> = [Arg, Arg, Arg, Arg, Arg, Arg]; -export type Arg7< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type -> = [Arg, Arg, Arg, Arg, Arg, Arg, Arg]; -export type Arg8< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type, - H extends Type -> = [Arg, Arg, Arg, Arg, Arg, Arg, Arg, Arg]; - -export type FnBody0 = Fn0; -export type FnBody1 = Fn, ScopeBody>; -export type FnBody2 = Fn2< - Sym, - Sym, - ScopeBody ->; -export type FnBody3 = Fn3< - Sym, - Sym, - Sym, - ScopeBody ->; -export type FnBody4< - A extends Type, - B extends Type, - C extends Type, - D extends Type -> = Fn4, Sym, Sym, Sym, ScopeBody>; -export type FnBody5< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type -> = Fn5, Sym, Sym, Sym, Sym, ScopeBody>; -export type FnBody6< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type -> = Fn6, Sym, Sym, Sym, Sym, Sym, ScopeBody>; -export type FnBody7< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type -> = Fn7, Sym, Sym, Sym, Sym, Sym, Sym, ScopeBody>; -export type FnBody8< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type, - H extends Type -> = Fn8< - Sym, - Sym, - Sym, - Sym, - Sym, - Sym, - Sym, - Sym, - ScopeBody ->; - -export type Func0 = Fn0>; -export type Func1 = Fn, FnCall>; -export type Func2 = Fn2< - Term, - Term, - FnCall ->; -export type Func3< - A extends Type, - B extends Type, - C extends Type, - T extends Type -> = Fn3, Term, Term, FnCall>; -export type Func4< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - T extends Type -> = Fn4, Term, Term, Term, FnCall>; -export type Func5< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - T extends Type -> = Fn5, Term, Term, Term, Term, FnCall>; -export type Func6< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - T extends Type -> = Fn6, Term, Term, Term, Term, Term, FnCall>; -export type Func7< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type, - T extends Type -> = Fn7< - Term, - Term, - Term, - Term, - Term, - Term, - Term, - FnCall ->; -export type Func8< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type, - H extends Type, - T extends Type -> = Fn8< - Term, - Term, - Term, - Term, - Term, - Term, - Term, - Term, - FnCall ->; - -export type SymQualifier = "in" | "out" | "inout"; - -export type SymType = "in" | "out" | "uni"; - -export type Precision = "lowp" | "mediump" | "highp"; - -export type ScopeBody = (Term | null | undefined)[]; - -export interface Term { - tag: Tag; - type: T; -} - -export interface Scoped { - scope: Scope; -} - -export interface Lit extends Term { - val: any; - info?: string; -} - -export interface Sym extends Term { - id: string; - opts: SymOpts; - init?: Term; -} - -export interface SymOpts { - /** - * If in global scope, used for: - * - * - `in` => attribute (in VS), varying (in FS) - * - `out` => varying (in VS), output (in FS) - * - * For parameters / fn args: - * - * - `in` => passed into a function - * - `out` => passed back out of a function, but not initialized - * - `inout` => passed both into and out of a function - */ - q?: SymQualifier; - /** - * Symbol type, only used for global scope in/out vars, e.g. - * attribute, varying, uniform. - */ - type?: SymType; - /** - * Const symbol - */ - const?: boolean; - /** - * Precision qualifier - */ - prec?: Precision; - /** - * Arrays only. Length - */ - num?: number; - /** - * Layout location - */ - loc?: number; -} - -export interface ArrayInit extends Term { - init: (Sym | Lit)[]; -} - -export interface Decl extends Term { - id: Sym; -} - -export interface Swizzle extends Term { - id: string; - val: Term; -} - -export interface Index extends Term { - id: Term<"int"> | Term<"uint">; - val: Term; -} - -export interface Assign extends Term { - l: Assignable; - r: Term; -} - -export interface Op1 extends Term { - op: Operator; - val: Term; - post?: boolean; -} - -export interface Op2 extends Term { - info?: string; - op: Operator; - l: Term; - r: Term; -} - -export interface Scope extends Term<"void"> { - body: Term[]; - global: boolean; -} - -export interface Branch extends Term<"void"> { - test: BoolTerm; - t: Scope; - f?: Scope; -} - -export interface Ternary extends Term { - test: BoolTerm; - t: Term; - f: Term; -} - -export interface ControlFlow extends Term<"void"> { - tag: "ctrl"; - id: string; -} - -export interface FuncReturn extends Term { - val?: Term; -} - -export interface FuncArg extends Term { - id: string; - opts: SymOpts; -} - -export interface Func extends Term, Scoped { - id: string; - args: Sym[]; - deps: Func[]; -} - -export interface TaggedFn0 extends Func0, Func { - args: []; -} - -export interface TaggedFn1 - extends Func1, - Func { - args: [Sym]; -} - -export interface TaggedFn2 - extends Func2, - Func { - args: [Sym, Sym]; -} - -export interface TaggedFn3< - A extends Type, - B extends Type, - C extends Type, - T extends Type -> extends Func3, Func { - args: [Sym, Sym, Sym]; -} - -export interface TaggedFn4< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - T extends Type -> extends Func4, Func { - args: [Sym, Sym, Sym, Sym]; -} - -export interface TaggedFn5< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - T extends Type -> extends Func5, Func { - args: [Sym, Sym, Sym, Sym, Sym]; -} - -export interface TaggedFn6< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - T extends Type -> extends Func6, Func { - args: [Sym, Sym, Sym, Sym, Sym, Sym]; -} - -export interface TaggedFn7< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type, - T extends Type -> extends Func7, Func { - args: [Sym, Sym, Sym, Sym, Sym, Sym, Sym]; -} - -export interface TaggedFn8< - A extends Type, - B extends Type, - C extends Type, - D extends Type, - E extends Type, - F extends Type, - G extends Type, - H extends Type, - T extends Type -> extends Func8, Func { - args: [Sym, Sym, Sym, Sym, Sym, Sym, Sym, Sym]; -} - -export interface FnCall extends Term { - id: string; - args: Term[]; - info?: string; - fn?: Func; -} - -export interface ForLoop extends Term<"void">, Scoped { - init?: Decl; - test: BoolTerm; - iter?: Term; -} - -export interface WhileLoop extends Term<"void">, Scoped { - test: BoolTerm; -} - -export interface TargetImpl extends Record> { - arg: Fn, T>; - array_init: Fn, T>; - assign: Fn, T>; - call: Fn, T>; - call_i: Fn, T>; - decl: Fn, T>; - fn: Fn, T>; - for: Fn; - idx: Fn, T>; - if: Fn; - lit: Fn, T>; - op1: Fn, T>; - op2: Fn, T>; - ret: Fn, T>; - scope: Fn; - swizzle: Fn, T>; - sym: Fn, T>; - ternary: Fn, T>; - while: Fn; -} diff --git a/packages/shader-ast/src/api/function.ts b/packages/shader-ast/src/api/function.ts new file mode 100644 index 0000000000..075c6c72d0 --- /dev/null +++ b/packages/shader-ast/src/api/function.ts @@ -0,0 +1,231 @@ +import { + Fn, + Fn0, + Fn2, + Fn3, + Fn4, + Fn5, + Fn6, + Fn7, + Fn8 +} from "@thi.ng/api"; +import { FnCall, Sym, Term } from "./nodes"; +import { SymOpts } from "./syms"; +import { Type } from "./types"; + +export type ScopeBody = (Term | null | undefined)[]; + +export type Arg = A | [A, string?, SymOpts?]; + +export type Arg1 = [Arg]; + +export type Arg2 = [Arg, Arg]; + +export type Arg3 = [ + Arg, + Arg, + Arg +]; + +export type Arg4< + A extends Type, + B extends Type, + C extends Type, + D extends Type +> = [Arg, Arg, Arg, Arg]; + +export type Arg5< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type +> = [Arg, Arg, Arg, Arg, Arg]; + +export type Arg6< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type +> = [Arg, Arg, Arg, Arg, Arg, Arg]; + +export type Arg7< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type +> = [Arg, Arg, Arg, Arg, Arg, Arg, Arg]; + +export type Arg8< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type, + H extends Type +> = [Arg, Arg, Arg, Arg, Arg, Arg, Arg, Arg]; + +export type FnBody0 = Fn0; + +export type FnBody1 = Fn, ScopeBody>; + +export type FnBody2 = Fn2< + Sym, + Sym, + ScopeBody +>; + +export type FnBody3 = Fn3< + Sym, + Sym, + Sym, + ScopeBody +>; + +export type FnBody4< + A extends Type, + B extends Type, + C extends Type, + D extends Type +> = Fn4, Sym, Sym, Sym, ScopeBody>; + +export type FnBody5< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type +> = Fn5, Sym, Sym, Sym, Sym, ScopeBody>; + +export type FnBody6< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type +> = Fn6, Sym, Sym, Sym, Sym, Sym, ScopeBody>; + +export type FnBody7< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type +> = Fn7, Sym, Sym, Sym, Sym, Sym, Sym, ScopeBody>; + +export type FnBody8< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type, + H extends Type +> = Fn8< + Sym, + Sym, + Sym, + Sym, + Sym, + Sym, + Sym, + Sym, + ScopeBody +>; + +export type Func0 = Fn0>; + +export type Func1 = Fn, FnCall>; + +export type Func2 = Fn2< + Term, + Term, + FnCall +>; + +export type Func3< + A extends Type, + B extends Type, + C extends Type, + T extends Type +> = Fn3, Term, Term, FnCall>; + +export type Func4< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + T extends Type +> = Fn4, Term, Term, Term, FnCall>; + +export type Func5< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + T extends Type +> = Fn5, Term, Term, Term, Term, FnCall>; + +export type Func6< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + T extends Type +> = Fn6, Term, Term, Term, Term, Term, FnCall>; + +export type Func7< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type, + T extends Type +> = Fn7< + Term, + Term, + Term, + Term, + Term, + Term, + Term, + FnCall +>; + +export type Func8< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type, + H extends Type, + T extends Type +> = Fn8< + Term, + Term, + Term, + Term, + Term, + Term, + Term, + Term, + FnCall +>; diff --git a/packages/shader-ast/src/api/nodes.ts b/packages/shader-ast/src/api/nodes.ts new file mode 100644 index 0000000000..f9d7c92766 --- /dev/null +++ b/packages/shader-ast/src/api/nodes.ts @@ -0,0 +1,216 @@ +import { + Func0, + Func1, + Func2, + Func3, + Func4, + Func5, + Func6, + Func7, + Func8 +} from "./function"; +import { Operator } from "./ops"; +import { SymOpts } from "./syms"; +import { Tag } from "./tags"; +import { BoolTerm } from "./terms"; +import { + Assignable, + Indexable, + Type, + Vec +} from "./types"; + +export interface Term { + tag: Tag; + type: T; +} + +export interface Scoped { + scope: Scope; +} + +export interface Lit extends Term { + val: any; + info?: string; +} + +export interface Sym extends Term { + id: string; + opts: SymOpts; + init?: Term; +} + +export interface ArrayInit extends Term { + init: (Sym | Lit)[]; +} + +export interface Decl extends Term { + id: Sym; +} + +export interface Swizzle extends Term { + id: string; + val: Term; +} + +export interface Index extends Term { + id: Term<"int"> | Term<"uint">; + val: Term; +} + +export interface Assign extends Term { + l: Assignable; + r: Term; +} + +export interface Op1 extends Term { + op: Operator; + val: Term; + post?: boolean; +} + +export interface Op2 extends Term { + info?: string; + op: Operator; + l: Term; + r: Term; +} + +export interface Scope extends Term<"void"> { + body: Term[]; + global: boolean; +} + +export interface Branch extends Term<"void"> { + test: BoolTerm; + t: Scope; + f?: Scope; +} + +export interface Ternary extends Term { + test: BoolTerm; + t: Term; + f: Term; +} + +export interface ControlFlow extends Term<"void"> { + tag: "ctrl"; + id: string; +} + +export interface FuncReturn extends Term { + val?: Term; +} + +export interface FuncArg extends Term { + id: string; + opts: SymOpts; +} + +export interface Func extends Term, Scoped { + id: string; + args: Sym[]; + deps: Func[]; +} + +export interface TaggedFn0 extends Func0, Func { + args: []; +} + +export interface TaggedFn1 + extends Func1, + Func { + args: [Sym]; +} + +export interface TaggedFn2 + extends Func2, + Func { + args: [Sym, Sym]; +} + +export interface TaggedFn3< + A extends Type, + B extends Type, + C extends Type, + T extends Type +> extends Func3, Func { + args: [Sym, Sym, Sym]; +} + +export interface TaggedFn4< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + T extends Type +> extends Func4, Func { + args: [Sym, Sym, Sym, Sym]; +} + +export interface TaggedFn5< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + T extends Type +> extends Func5, Func { + args: [Sym, Sym, Sym, Sym, Sym]; +} + +export interface TaggedFn6< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + T extends Type +> extends Func6, Func { + args: [Sym, Sym, Sym, Sym, Sym, Sym]; +} + +export interface TaggedFn7< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type, + T extends Type +> extends Func7, Func { + args: [Sym, Sym, Sym, Sym, Sym, Sym, Sym]; +} + +export interface TaggedFn8< + A extends Type, + B extends Type, + C extends Type, + D extends Type, + E extends Type, + F extends Type, + G extends Type, + H extends Type, + T extends Type +> extends Func8, Func { + args: [Sym, Sym, Sym, Sym, Sym, Sym, Sym, Sym]; +} + +export interface FnCall extends Term { + id: string; + args: Term[]; + info?: string; + fn?: Func; +} + +export interface ForLoop extends Term<"void">, Scoped { + init?: Decl; + test: BoolTerm; + iter?: Term; +} + +export interface WhileLoop extends Term<"void">, Scoped { + test: BoolTerm; +} diff --git a/packages/shader-ast/src/api/ops.ts b/packages/shader-ast/src/api/ops.ts new file mode 100644 index 0000000000..9a1ef9ab7f --- /dev/null +++ b/packages/shader-ast/src/api/ops.ts @@ -0,0 +1,9 @@ +export type MathOperator = "+" | "-" | "*" | "/" | "%" | "++" | "--"; +export type LogicOperator = "!" | "||" | "&&"; +export type ComparisonOperator = "<" | "<=" | "==" | "!=" | ">=" | ">"; +export type BitOperator = "<<" | ">>" | "|" | "&" | "^" | "~"; +export type Operator = + | MathOperator + | LogicOperator + | ComparisonOperator + | BitOperator; diff --git a/packages/shader-ast/src/api/precision.ts b/packages/shader-ast/src/api/precision.ts new file mode 100644 index 0000000000..954e2c97b2 --- /dev/null +++ b/packages/shader-ast/src/api/precision.ts @@ -0,0 +1 @@ +export type Precision = "lowp" | "mediump" | "highp"; diff --git a/packages/shader-ast/src/api/swizzles.ts b/packages/shader-ast/src/api/swizzles.ts new file mode 100644 index 0000000000..33b6da7194 --- /dev/null +++ b/packages/shader-ast/src/api/swizzles.ts @@ -0,0 +1,29 @@ +// swizzle gen: +// console.log([...permutations("xyz","xyz")].map((x) =>`"${x.join("")}"`).join(" | ")) + +export type Swizzle2_1 = "x" | "y"; +export type Swizzle2_2 = "xx" | "xy" | "yx" | "yy"; +// prettier-ignore +export type Swizzle2_3 = "xxx" | "xxy" | "xyx" | "xyy" | "yxx" | "yxy" | "yyx" | "yyy"; +// prettier-ignore +export type Swizzle2_4 = "xxxx" | "xxxy" | "xxyx" | "xxyy" | "xyxx" | "xyxy" | "xyyx" | "xyyy" | "yxxx" | "yxxy" | "yxyx" | "yxyy" | "yyxx" | "yyxy" | "yyyx" | "yyyy"; + +export type Swizzle3_1 = "x" | "y" | "z"; +// prettier-ignore +export type Swizzle3_2 = "xx" | "xy" | "xz" | "yx" | "yy" | "yz" | "zx" | "zy" | "zz"; +// prettier-ignore +export type Swizzle3_3 = "xxx" | "xxy" | "xxz" | "xyx" | "xyy" | "xyz" | "xzx" | "xzy" | "xzz" | "yxx" | "yxy" | "yxz" | "yyx" | "yyy" | "yyz" | "yzx" | "yzy" | "yzz" | "zxx" | "zxy" | "zxz" | "zyx" | "zyy" | "zyz" | "zzx" | "zzy" | "zzz"; +// prettier-ignore +export type Swizzle3_4 = "xxxx" | "xxxy" | "xxxz" | "xxyx" | "xxyy" | "xxyz" | "xxzx" | "xxzy" | "xxzz" | "xyxx" | "xyxy" | "xyxz" | "xyyx" | "xyyy" | "xyyz" | "xyzx" | "xyzy" | "xyzz" | "xzxx" | "xzxy" | "xzxz" | "xzyx" | "xzyy" | "xzyz" | "xzzx" | "xzzy" | "xzzz" | "yxxx" | "yxxy" | "yxxz" | "yxyx" | "yxyy" | "yxyz" | "yxzx" | "yxzy" | "yxzz" | "yyxx" | "yyxy" | "yyxz" | "yyyx" | "yyyy" | "yyyz" | "yyzx" | "yyzy" | "yyzz" | "yzxx" | "yzxy" | "yzxz" | "yzyx" | "yzyy" | "yzyz" | "yzzx" | "yzzy" | "yzzz" | "zxxx" | "zxxy" | "zxxz" | "zxyx" | "zxyy" | "zxyz" | "zxzx" | "zxzy" | "zxzz" | "zyxx" | "zyxy" | "zyxz" | "zyyx" | "zyyy" | "zyyz" | "zyzx" | "zyzy" | "zyzz" | "zzxx" | "zzxy" | "zzxz" | "zzyx" | "zzyy" | "zzyz" | "zzzx" | "zzzy" | "zzzz"; + +export type Swizzle4_1 = "x" | "y" | "z" | "w"; +// prettier-ignore +export type Swizzle4_2 = "xx" | "xy" | "xz" | "xw" | "yx" | "yy" | "yz" | "yw" | "zx" | "zy" | "zz" | "zw" | "wx" | "wy" | "wz" | "ww"; +// prettier-ignore +export type Swizzle4_3 = "xxx" | "xxy" | "xxz" | "xxw" | "xyx" | "xyy" | "xyz" | "xyw" | "xzx" | "xzy" | "xzz" | "xzw" | "xwx" | "xwy" | "xwz" | "xww" | "yxx" | "yxy" | "yxz" | "yxw" | "yyx" | "yyy" | "yyz" | "yyw" | "yzx" | "yzy" | "yzz" | "yzw" | "ywx" | "ywy" | "ywz" | "yww" | "zxx" | "zxy" | "zxz" | "zxw" | "zyx" | "zyy" | "zyz" | "zyw" | "zzx" | "zzy" | "zzz" | "zzw" | "zwx" | "zwy" | "zwz" | "zww" | "wxx" | "wxy" | "wxz" | "wxw" | "wyx" | "wyy" | "wyz" | "wyw" | "wzx" | "wzy" | "wzz" | "wzw" | "wwx" | "wwy" | "wwz" | "www"; +// prettier-ignore +export type Swizzle4_4 = "xxxx" | "xxxy" | "xxxz" | "xxxw" | "xxyx" | "xxyy" | "xxyz" | "xxyw" | "xxzx" | "xxzy" | "xxzz" | "xxzw" | "xxwx" | "xxwy" | "xxwz" | "xxww" | "xyxx" | "xyxy" | "xyxz" | "xyxw" | "xyyx" | "xyyy" | "xyyz" | "xyyw" | "xyzx" | "xyzy" | "xyzz" | "xyzw" | "xywx" | "xywy" | "xywz" | "xyww" | "xzxx" | "xzxy" | "xzxz" | "xzxw" | "xzyx" | "xzyy" | "xzyz" | "xzyw" | "xzzx" | "xzzy" | "xzzz" | "xzzw" | "xzwx" | "xzwy" | "xzwz" | "xzww" | "xwxx" | "xwxy" | "xwxz" | "xwxw" | "xwyx" | "xwyy" | "xwyz" | "xwyw" | "xwzx" | "xwzy" | "xwzz" | "xwzw" | "xwwx" | "xwwy" | "xwwz" | "xwww" | "yxxx" | "yxxy" | "yxxz" | "yxxw" | "yxyx" | "yxyy" | "yxyz" | "yxyw" | "yxzx" | "yxzy" | "yxzz" | "yxzw" | "yxwx" | "yxwy" | "yxwz" | "yxww" | "yyxx" | "yyxy" | "yyxz" | "yyxw" | "yyyx" | "yyyy" | "yyyz" | "yyyw" | "yyzx" | "yyzy" | "yyzz" | "yyzw" | "yywx" | "yywy" | "yywz" | "yyww" | "yzxx" | "yzxy" | "yzxz" | "yzxw" | "yzyx" | "yzyy" | "yzyz" | "yzyw" | "yzzx" | "yzzy" | "yzzz" | "yzzw" | "yzwx" | "yzwy" | "yzwz" | "yzww" | "ywxx" | "ywxy" | "ywxz" | "ywxw" | "ywyx" | "ywyy" | "ywyz" | "ywyw" | "ywzx" | "ywzy" | "ywzz" | "ywzw" | "ywwx" | "ywwy" | "ywwz" | "ywww" | "zxxx" | "zxxy" | "zxxz" | "zxxw" | "zxyx" | "zxyy" | "zxyz" | "zxyw" | "zxzx" | "zxzy" | "zxzz" | "zxzw" | "zxwx" | "zxwy" | "zxwz" | "zxww" | "zyxx" | "zyxy" | "zyxz" | "zyxw" | "zyyx" | "zyyy" | "zyyz" | "zyyw" | "zyzx" | "zyzy" | "zyzz" | "zyzw" | "zywx" | "zywy" | "zywz" | "zyww" | "zzxx" | "zzxy" | "zzxz" | "zzxw" | "zzyx" | "zzyy" | "zzyz" | "zzyw" | "zzzx" | "zzzy" | "zzzz" | "zzzw" | "zzwx" | "zzwy" | "zzwz" | "zzww" | "zwxx" | "zwxy" | "zwxz" | "zwxw" | "zwyx" | "zwyy" | "zwyz" | "zwyw" | "zwzx" | "zwzy" | "zwzz" | "zwzw" | "zwwx" | "zwwy" | "zwwz" | "zwww" | "wxxx" | "wxxy" | "wxxz" | "wxxw" | "wxyx" | "wxyy" | "wxyz" | "wxyw" | "wxzx" | "wxzy" | "wxzz" | "wxzw" | "wxwx" | "wxwy" | "wxwz" | "wxww" | "wyxx" | "wyxy" | "wyxz" | "wyxw" | "wyyx" | "wyyy" | "wyyz" | "wyyw" | "wyzx" | "wyzy" | "wyzz" | "wyzw" | "wywx" | "wywy" | "wywz" | "wyww" | "wzxx" | "wzxy" | "wzxz" | "wzxw" | "wzyx" | "wzyy" | "wzyz" | "wzyw" | "wzzx" | "wzzy" | "wzzz" | "wzzw" | "wzwx" | "wzwy" | "wzwz" | "wzww" | "wwxx" | "wwxy" | "wwxz" | "wwxw" | "wwyx" | "wwyy" | "wwyz" | "wwyw" | "wwzx" | "wwzy" | "wwzz" | "wwzw" | "wwwx" | "wwwy" | "wwwz" | "wwww"; + +export type Swizzle2 = Swizzle2_1 | Swizzle2_2 | Swizzle2_3 | Swizzle2_4; +export type Swizzle3 = Swizzle3_1 | Swizzle3_2 | Swizzle3_3 | Swizzle3_4; +export type Swizzle4 = Swizzle4_1 | Swizzle4_2 | Swizzle4_3 | Swizzle4_4; diff --git a/packages/shader-ast/src/api/syms.ts b/packages/shader-ast/src/api/syms.ts new file mode 100644 index 0000000000..ef8d34abf2 --- /dev/null +++ b/packages/shader-ast/src/api/syms.ts @@ -0,0 +1,72 @@ +import { Sym } from "./nodes"; +import { Precision } from "./precision"; + +export type SymQualifier = "in" | "out" | "inout"; + +export type SymType = "in" | "out" | "uni"; + +export type BoolSym = Sym<"bool">; +export type FloatSym = Sym<"float">; +export type IntSym = Sym<"int">; +export type UintSym = Sym<"uint">; +export type Vec2Sym = Sym<"vec2">; +export type Vec3Sym = Sym<"vec3">; +export type Vec4Sym = Sym<"vec4">; +export type IVec2Sym = Sym<"ivec2">; +export type IVec3Sym = Sym<"ivec3">; +export type IVec4Sym = Sym<"ivec4">; +export type UVec2Sym = Sym<"uvec2">; +export type UVec3Sym = Sym<"uvec3">; +export type UVec4Sym = Sym<"uvec4">; +export type BVec2Sym = Sym<"bvec2">; +export type BVec3Sym = Sym<"bvec3">; +export type BVec4Sym = Sym<"bvec4">; +export type Mat2Sym = Sym<"mat2">; +export type Mat3Sym = Sym<"mat3">; +export type Mat4Sym = Sym<"mat4">; +export type Sampler2DSym = Sym<"sampler2D">; +export type Sampler3DSym = Sym<"sampler3D">; +export type SamplerCubeSym = Sym<"samplerCube">; +export type ISampler2DSym = Sym<"isampler2D">; +export type ISampler3DSym = Sym<"isampler3D">; +export type ISamplerCubeSym = Sym<"isamplerCube">; +export type USampler2DSym = Sym<"usampler2D">; +export type USampler3DSym = Sym<"usampler3D">; +export type USamplerCubeSym = Sym<"usamplerCube">; + +export interface SymOpts { + /** + * If in global scope, used for: + * + * - `in` => attribute (in VS), varying (in FS) + * - `out` => varying (in VS), output (in FS) + * + * For parameters / fn args: + * + * - `in` => passed into a function + * - `out` => passed back out of a function, but not initialized + * - `inout` => passed both into and out of a function + */ + q?: SymQualifier; + /** + * Symbol type, only used for global scope in/out vars, e.g. + * attribute, varying, uniform. + */ + type?: SymType; + /** + * Const symbol + */ + const?: boolean; + /** + * Precision qualifier + */ + prec?: Precision; + /** + * Arrays only. Length + */ + num?: number; + /** + * Layout location + */ + loc?: number; +} diff --git a/packages/shader-ast/src/api/tags.ts b/packages/shader-ast/src/api/tags.ts new file mode 100644 index 0000000000..a73fc24f1d --- /dev/null +++ b/packages/shader-ast/src/api/tags.ts @@ -0,0 +1,21 @@ +export type Tag = + | "arg" + | "array_init" + | "assign" + | "call" + | "call_i" + | "ctrl" + | "decl" + | "fn" + | "for" + | "idx" + | "if" + | "lit" + | "op1" + | "op2" + | "ret" + | "scope" + | "swizzle" + | "sym" + | "ternary" + | "while"; diff --git a/packages/shader-ast/src/api/target.ts b/packages/shader-ast/src/api/target.ts new file mode 100644 index 0000000000..b2a000ad91 --- /dev/null +++ b/packages/shader-ast/src/api/target.ts @@ -0,0 +1,44 @@ +import { Fn } from "@thi.ng/api"; +import { + ArrayInit, + Assign, + Branch, + Decl, + FnCall, + ForLoop, + Func, + FuncArg, + FuncReturn, + Index, + Lit, + Op1, + Op2, + Scope, + Swizzle, + Sym, + Ternary, + WhileLoop +} from "./nodes"; +import { Tag } from "./tags"; + +export interface TargetImpl extends Record> { + arg: Fn, T>; + array_init: Fn, T>; + assign: Fn, T>; + call: Fn, T>; + call_i: Fn, T>; + decl: Fn, T>; + fn: Fn, T>; + for: Fn; + idx: Fn, T>; + if: Fn; + lit: Fn, T>; + op1: Fn, T>; + op2: Fn, T>; + ret: Fn, T>; + scope: Fn; + swizzle: Fn, T>; + sym: Fn, T>; + ternary: Fn, T>; + while: Fn; +} diff --git a/packages/shader-ast/src/api/terms.ts b/packages/shader-ast/src/api/terms.ts new file mode 100644 index 0000000000..09882cc085 --- /dev/null +++ b/packages/shader-ast/src/api/terms.ts @@ -0,0 +1,30 @@ +import { Term } from "./nodes"; + +export type BoolTerm = Term<"bool">; +export type FloatTerm = Term<"float">; +export type IntTerm = Term<"int">; +export type UintTerm = Term<"uint">; +export type Vec2Term = Term<"vec2">; +export type Vec3Term = Term<"vec3">; +export type Vec4Term = Term<"vec4">; +export type IVec2Term = Term<"ivec2">; +export type IVec3Term = Term<"ivec3">; +export type IVec4Term = Term<"ivec4">; +export type UVec2Term = Term<"uvec2">; +export type UVec3Term = Term<"uvec3">; +export type UVec4Term = Term<"uvec4">; +export type BVec2Term = Term<"bvec2">; +export type BVec3Term = Term<"bvec3">; +export type BVec4Term = Term<"bvec4">; +export type Mat2Term = Term<"mat2">; +export type Mat3Term = Term<"mat3">; +export type Mat4Term = Term<"mat4">; +export type Sampler2DTerm = Term<"sampler2D">; +export type Sampler3DTerm = Term<"sampler3D">; +export type SamplerCubeTerm = Term<"samplerCube">; +export type ISampler2DTerm = Term<"isampler2D">; +export type ISampler3DTerm = Term<"isampler3D">; +export type ISamplerCubeTerm = Term<"isamplerCube">; +export type USampler2DTerm = Term<"usampler2D">; +export type USampler3DTerm = Term<"usampler3D">; +export type USamplerCubeTerm = Term<"usamplerCube">; diff --git a/packages/shader-ast/src/api/types.ts b/packages/shader-ast/src/api/types.ts new file mode 100644 index 0000000000..500d61e8f6 --- /dev/null +++ b/packages/shader-ast/src/api/types.ts @@ -0,0 +1,159 @@ +import { Index, Swizzle, Sym } from "./nodes"; +import { + BoolTerm, + FloatTerm, + IntTerm, + UintTerm +} from "./terms"; + +export type Type = + | "void" + | "bool" + | "bool[]" + | "float" + | "float[]" + | "int" + | "int[]" + | "uint" + | "uint[]" + | "vec2" + | "vec2[]" + | "vec3" + | "vec3[]" + | "vec4" + | "vec4[]" + | "ivec2" + | "ivec2[]" + | "ivec3" + | "ivec3[]" + | "ivec4" + | "ivec4[]" + | "uvec2" + | "uvec2[]" + | "uvec3" + | "uvec3[]" + | "uvec4" + | "uvec4[]" + | "bvec2" + | "bvec2[]" + | "bvec3" + | "bvec3[]" + | "bvec4" + | "bvec4[]" + | "mat2" + | "mat2[]" + | "mat3" + | "mat3[]" + | "mat4" + | "mat4[]" + | "sampler2D" + | "sampler2D[]" + | "sampler3D" + | "sampler3D[]" + | "samplerCube" + | "samplerCube[]" + | "sampler2DShadow" + | "sampler2DShadow[]" + | "samplerCubeShadow" + | "samplerCubeShadow[]" + | "isampler2D" + | "isampler2D[]" + | "isampler3D" + | "isampler3D[]" + | "isamplerCube" + | "isamplerCube[]" + | "usampler2D" + | "usampler2D[]" + | "usampler3D" + | "usampler3D[]" + | "usamplerCube" + | "usamplerCube[]"; + +export interface ArrayTypeMap { + bool: "bool[]"; + float: "float[]"; + int: "int[]"; + uint: "uint[]"; + vec2: "vec2[]"; + vec3: "vec3[]"; + vec4: "vec4[]"; + ivec2: "ivec2[]"; + ivec3: "ivec3[]"; + ivec4: "ivec4[]"; + uvec2: "uvec2[]"; + uvec3: "uvec3[]"; + uvec4: "uvec4[]"; + bvec2: "bvec2[]"; + bvec3: "bvec3[]"; + bvec4: "bvec4[]"; + mat2: "mat2[]"; + mat3: "mat3[]"; + mat4: "mat4[]"; + sampler2D: "sampler2D[]"; + sampler3D: "sampler3D[]"; + samplerCube: "samplerCube[]"; + sampler2DShadow: "sampler2DShadow[]"; + samplerCubeShadow: "samplerCubeShadow[]"; + isampler2D: "isampler2D[]"; + isampler3D: "isampler3D[]"; + isamplerCube: "isamplerCube[]"; + usampler2D: "usampler2D[]"; + usampler3D: "usampler3D[]"; + usamplerCube: "usamplerCube[]"; +} + +export interface IndexTypeMap { + "bool[]": "bool"; + "float[]": "float"; + "int[]": "int"; + "uint[]": "uint"; + "vec2[]": "vec2"; + "vec3[]": "vec3"; + "vec4[]": "vec4"; + "ivec2[]": "ivec2"; + "ivec3[]": "ivec3"; + "ivec4[]": "ivec4"; + "uvec2[]": "uvec2"; + "uvec3[]": "uvec3"; + "uvec4[]": "uvec4"; + "bvec2[]": "bvec2"; + "bvec3[]": "bvec3"; + "bvec4[]": "bvec4"; +} + +export interface MatIndexTypeMap { + mat2: "vec2"; + mat3: "vec3"; + mat4: "vec4"; +} + +export type Indexable = keyof IndexTypeMap; + +export type Assignable = Sym | Swizzle | Index; + +export type Vec = "vec2" | "vec3" | "vec4"; +export type IVec = "ivec2" | "ivec3" | "ivec4"; +export type UVec = "uvec2" | "uvec3" | "uvec4"; +export type BVec = "bvec2" | "bvec3" | "bvec4"; +export type Mat = "mat2" | "mat3" | "mat4"; +export type Sampler = + | "sampler2D" + | "sampler3D" + | "samplerCube" + | "sampler2DShadow" + | "samplerCubeShadow" + | "isampler2D" + | "isampler3D" + | "isamplerCube" + | "usampler2D" + | "usampler3D" + | "usamplerCube"; + +export type Prim = "float" | Vec; +export type Int = "int" | "uint"; +export type Comparable = "float" | Int; +export type Numeric = number | FloatTerm | IntTerm | UintTerm; +export type NumericF = number | FloatTerm; +export type NumericI = number | IntTerm; +export type NumericU = number | UintTerm; +export type NumericB = boolean | Numeric | BoolTerm; diff --git a/packages/shader-ast/src/ast.ts b/packages/shader-ast/src/ast.ts deleted file mode 100644 index 24a52086f7..0000000000 --- a/packages/shader-ast/src/ast.ts +++ /dev/null @@ -1,1416 +0,0 @@ -import { - assert, - Fn, - Fn2, - IObjectOf, - Select4 -} from "@thi.ng/api"; -import { - isArray, - isBoolean, - isNumber, - isString -} from "@thi.ng/checks"; -import { DGraph } from "@thi.ng/dgraph"; -import { illegalArgs } from "@thi.ng/errors"; -import { - Arg, - Arg1, - Arg2, - Arg3, - Arg4, - Arg5, - Arg6, - Arg7, - Arg8, - ArrayTypeMap, - Assign, - Assignable, - BoolTerm, - Branch, - BVec, - BVec2Term, - BVec3Term, - BVec4Term, - Comparable, - ComparisonOperator, - ControlFlow, - Decl, - FloatTerm, - FnBody0, - FnBody1, - FnBody2, - FnBody3, - FnBody4, - FnBody5, - FnBody6, - FnBody7, - FnBody8, - FnCall, - ForLoop, - Func, - FuncArg, - FuncReturn, - Index, - Indexable, - IndexTypeMap, - Int, - IntTerm, - IVec, - IVec2Term, - IVec3Term, - IVec4Term, - Lit, - Mat, - Mat2Term, - Mat3Term, - Mat4Term, - MatIndexTypeMap, - NumericB, - NumericF, - NumericI, - NumericU, - Op1, - Op2, - Operator, - Prim, - Scope, - ScopeBody, - Swizzle, - Swizzle2, - Swizzle2_1, - Swizzle2_2, - Swizzle2_3, - Swizzle3, - Swizzle3_1, - Swizzle3_2, - Swizzle3_3, - Swizzle4, - Swizzle4_1, - Swizzle4_2, - Swizzle4_3, - Sym, - SymOpts, - TaggedFn0, - TaggedFn1, - TaggedFn2, - TaggedFn3, - TaggedFn4, - TaggedFn5, - TaggedFn6, - TaggedFn7, - TaggedFn8, - Term, - Ternary, - Type, - UintTerm, - UVec, - UVec2Term, - UVec3Term, - UVec4Term, - Vec, - Vec2Term, - Vec3Term, - Vec4Term, - WhileLoop -} from "./api"; - -let symID = 0; - -const RE_VEC = /^[iub]?vec[234]$/; -const RE_MAT = /^mat[234]$/; - -/** - * Helper for deterministic code generation / testing. Resets sym ID - * counter. - */ -export const resetSymID = () => (symID = 0); - -/** - * Generates a new symbol name, e.g. `_sa2`. Uses base36 for counter to - * keep names short. - */ -export const gensym = () => `_s${(symID++).toString(36)}`; - -/** - * Returns true, if given term evaluates to a boolean value. - */ -export const isBool = (t: Term) => t.type === "bool"; - -/** - * Returns true, if given term evaluates to a float value. - */ -export const isFloat = (t: Term) => t.type === "float"; - -/** - * Returns true, if given term evaluates to a signed integer value. - */ -export const isInt = (t: Term) => t.type === "int"; - -/** - * Returns true, if given term evaluates to an unsigned integer value. - */ -export const isUint = (t: Term) => t.type === "uint"; - -/** - * Returns true, if given term is a literal. - */ -export const isLit = (t: Term) => t.tag === "lit"; - -/** - * Returns true, if given term is a float literal. - */ -export const isLitFloat = (t: Term) => isLit(t) && isFloat(t); - -/** - * Returns true, if given term is a signed integer literal. - */ -export const isLitInt = (t: Term) => isLit(t) && isInt(t); - -/** - * Returns true, if given term is a numeric literal (float, int, uint). - */ -export const isLitNumeric = (t: Term) => - isLit(t) && (isFloat(t) || isInt(t) || isUint(t)); - -/** - * Returns true, if given term evaluates to a vector value (vec, ivec, bvec). - */ -export const isVec = (t: Term) => RE_VEC.test(t.type); - -/** - * Returns true, if given term evaluates to a matrix value. - */ -export const isMat = (t: Term) => RE_MAT.test(t.type); - -/** - * Returns base type for given term. Used for array ops. - * - * ``` - * itemType("vec2[]") => "vec2" - * ``` - */ -export const itemType = (type: Type) => type.replace("[]", ""); - -/** - * Takes a numeric term and a plain number, returns number wrapped in - * typed literal compatible with term. - * - * @param t - * @param x - */ -export const numberWithMatchingType = (t: Term, x: number) => { - const id = t.type[0]; - return id === "i" - ? int(x) - : id === "u" - ? uint(x) - : id === "b" - ? bool(x) - : float(x); -}; - -export const matchingPrimFor = ( - t: Term, - x: FloatTerm -): Term => { - const ctor = ({ vec2, vec3, vec4 })[t.type]; - return ctor ? ctor(x) : x; -}; - -/** - * Helper function for `walk()`. Returns child nodes for any control - * flow nodes containing a child scope. - * - * @see allChildren - */ -export const scopedChildren = (t: Term) => - t.tag === "fn" || t.tag === "for" || t.tag == "while" - ? (>t).scope.body - : t.tag === "if" - ? (t).f - ? (t).t.body.concat((t).f!.body) - : (t).t.body - : undefined; - -/** - * Helper function for `walk()`. Returns an array of all child nodes for - * a given term (if any). - * - * @see scopedChildren - */ -export const allChildren = (t: Term) => - t.tag === "scope" - ? (t).body - : t.tag === "fn" || t.tag === "for" || t.tag == "while" - ? (>t).scope.body - : t.tag === "if" - ? (t).f - ? (t).t.body.concat((t).f!.body) - : (t).t.body - : t.tag === "ternary" - ? [(>t).t, (>t).f] - : t.tag === "ret" - ? [(>t).val] - : t.tag === "call" || t.tag === "call_i" - ? (>t).args - : t.tag === "sym" && (>t).init - ? [(>t).init] - : t.tag === "decl" - ? [(>t).id] - : t.tag === "op1" || t.tag === "swizzle" - ? [(>t).val] - : t.tag === "op2" - ? [(>t).l, (>t).r] - : t.tag === "assign" - ? [(>t).r] - : isVec(t) || isMat(t) - ? (>t).val - : undefined; - -/** - * Traverses given AST in depth-first order and applies `visit` and - * `children` fns to each node. Descends only further if `children` - * returns an array of child nodes. The `visit` function must accept 2 - * args: the accumulator (`acc`) given to `walk` and a tree node. The - * return value of `visit` becomes the new `acc` value, much like in a - * reduce operation. `walk` itself returns the final `acc`. - * - * If `pre` is true (default), the `visit` function will be called prior - * to visiting a node's children. If false, the visitor is called on the - * way back up. - * - * @param visit - * @param children - * @param acc - * @param tree - * @param pre - */ -export const walk = ( - visit: Fn2, T>, - children: Fn, Term[] | undefined>, - acc: T, - tree: Term | Term[], - pre = true -) => { - if (isArray(tree)) { - tree.forEach((x) => (acc = walk(visit, children, acc, x, pre))); - } else { - pre && (acc = visit(acc, tree)); - const c = children(tree); - c && (acc = walk(visit, children, acc, c, pre)); - !pre && (acc = visit(acc, tree)); - } - return acc; -}; - -/** - * Builds dependency graph of given function, by recursively adding all - * function dependencies. Returns graph. - * - * @param fn - * @param graph - */ -export const buildCallGraph = ( - fn: Func, - graph: DGraph> = new DGraph() -): DGraph> => - fn.deps && fn.deps.length - ? fn.deps.reduce( - (graph, d) => buildCallGraph(d, graph.addDependency(fn, d)), - graph - ) - : graph.addNode(fn); - -export const lit = ( - type: T, - val: any, - info?: string -): Lit => ({ - tag: "lit", - type, - info, - val -}); - -export const bool = (x: NumericB) => lit("bool", isNumber(x) ? !!x : x); - -export const float = (x: NumericB) => - lit("float", isBoolean(x) ? (x) & 1 : x); - -export const int = (x: NumericB) => - lit("int", isBoolean(x) ? (x) & 1 : isNumber(x) ? x | 0 : x); - -export const uint = (x: NumericB) => - lit("uint", isBoolean(x) ? (x) & 1 : isNumber(x) ? x >>> 0 : x); - -export const TRUE = lit("bool", true); -export const FALSE = lit("bool", false); - -export const FLOAT0: FloatTerm = float(0); -export const FLOAT1: FloatTerm = float(1); -export const FLOAT2: FloatTerm = float(2); -export const FLOAT05: FloatTerm = float(0.5); - -export const INT0: IntTerm = int(0); -export const INT1: IntTerm = int(1); - -export const UINT0: UintTerm = uint(0); -export const UINT1: UintTerm = uint(1); - -export const PI: FloatTerm = float(Math.PI); -export const TAU: FloatTerm = float(Math.PI * 2); -export const HALF_PI: FloatTerm = float(Math.PI / 2); -export const SQRT2: FloatTerm = float(Math.SQRT2); - -const wrap = (type: T, ctor: Fn>) => ( - x?: any -): Term | undefined => - isNumber(x) - ? ctor(x) - : x !== undefined && !isVec(x) && x.type !== type - ? ctor(x) - : x; - -/** - * Takes a plain number or numeric term and wraps it as float literal if - * needed. - * - * @param x - */ -export const wrapFloat = wrap("float", float); - -/** - * Takes a plain number or numeric term and wraps it as signed integer - * literal if needed. - * - * @param x - */ -export const wrapInt = wrap("int", int); - -/** - * Takes a plain number or numeric term and wraps it as unsigned integer - * literal if needed. - * - * @param x - */ -export const wrapUint = wrap("uint", uint); - -/** - * Takes a plain number or numeric term and wraps it as boolean literal - * if needed. - * - * @param x - */ -export const wrapBool = wrap("bool", bool); - -export function sym(init: Term): Sym; -export function sym(type: T): Sym; -export function sym(type: T, opts: SymOpts): Sym; -export function sym(type: T, init: Term): Sym; -export function sym(type: T, id: string): Sym; -// prettier-ignore -export function sym(type: T, id: string, opts: SymOpts): Sym; -// prettier-ignore -export function sym(type: T, opts: SymOpts, init: Term): Sym; -// prettier-ignore -export function sym(type: T, id: string, opts: SymOpts, init: Term): Sym; -export function sym(type: any, ...xs: any[]): Sym { - let id: string; - let opts: SymOpts; - let init: Term; - switch (xs.length) { - case 0: - if (!isString(type)) { - init = type; - type = init.type; - } - break; - case 1: - if (isString(xs[0])) { - id = xs[0]; - } else if (xs[0].tag) { - init = xs[0]; - } else { - opts = xs[0]; - } - break; - case 2: - if (isString(xs[0])) { - [id, opts] = xs; - } else { - [opts, init] = xs; - } - break; - case 3: - [id, opts, init] = xs; - break; - default: - illegalArgs(); - } - return { - tag: "sym", - type, - id: id! || gensym(), - opts: opts! || {}, - init: init! - }; -} - -export const constSym = ( - type: T, - id?: string, - opts?: SymOpts, - init?: Term -) => sym(type, id || gensym(), { const: true, ...opts }, init!); - -/** - * Defines a new symbol with optional initial array values. - * - * Important: Array initializers are UNSUPPORTED in GLSL ES v1 (WebGL), - * any code using such initializers will only work under WebGL2 or other - * targets. - */ -export const arraySym = ( - type: T, - id?: string, - opts: SymOpts = {}, - init?: (Lit | Sym)[] -): Sym => { - if (init && opts.num == null) { - opts.num = init.length; - } - assert(opts.num != null, "missing array length"); - init && - assert( - opts.num === init.length, - `expected ${opts.num} items in array, but got ${init.length}` - ); - const atype = (type + "[]"); - return { - tag: "sym", - type: atype, - id: id || gensym(), - opts, - init: init - ? { - tag: "array_init", - type: atype, - init - } - : undefined - }; -}; - -export const input = (type: T, id: string, opts?: SymOpts) => - sym(type, id, { q: "in", type: "in", ...opts }); - -export const output = (type: T, id: string, opts?: SymOpts) => - sym(type, id, { q: "out", type: "out", ...opts }); - -export const uniform = (type: T, id: string, opts?: SymOpts) => - sym(type, id, { q: "in", type: "uni", ...opts }); - -const decl = (id: Sym): Decl => ({ - tag: "decl", - type: id.type, - id -}); - -// prettier-ignore -export function $(a: Vec2Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: Vec3Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: Vec4Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: IVec2Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: IVec3Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: IVec4Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: UVec2Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: UVec3Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: UVec4Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: BVec2Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: BVec3Term, id: T): Swizzle>; -// prettier-ignore -export function $(a: BVec4Term, id: T): Swizzle>; -export function $(val: Term, id: string): Swizzle { - const type = val.type[0]; - const rtype = (a: Type, b: string) => - id.length === 1 ? a : (b + id.length); - return { - tag: "swizzle", - type: - type === "i" - ? rtype("int", "ivec") - : type === "u" - ? rtype("uint", "uvec") - : type === "b" - ? rtype("bool", "bvec") - : rtype("float", "vec"), - val, - id - }; -} - -export const $x = ( - val: Term -): Swizzle> => - $(val, "x"); - -export const $y = ( - val: Term -): Swizzle> => - $(val, "y"); - -export const $z = < - T extends - | "vec3" - | "vec4" - | "ivec3" - | "ivec4" - | "uvec3" - | "uvec4" - | "bvec3" - | "bvec4" ->( - val: Term -): Swizzle> => - $(val, "z"); - -export const $w = ( - val: Term -): Swizzle> => - $(val, "w"); - -export function $xy(val: Term): Swizzle<"vec2">; -export function $xy(val: Term): Swizzle<"ivec2">; -export function $xy(val: Term): Swizzle<"uvec2">; -export function $xy(val: Term): Swizzle<"bvec2">; -export function $xy(val: any): Swizzle { - return $(val, "xy"); -} - -export function $xyz(val: Term<"vec3" | "vec4">): Swizzle<"vec3">; -export function $xyz(val: Term<"ivec3" | "ivec4">): Swizzle<"ivec3">; -export function $xyz(val: Term<"uvec3" | "uvec4">): Swizzle<"uvec3">; -export function $xyz(val: Term<"bvec3" | "bvec4">): Swizzle<"bvec3">; -export function $xyz(val: any): Swizzle { - return $(val, "xyz"); -} - -export const index = ( - val: Sym, - id: NumericI | UintTerm -): Index => ({ - tag: "idx", - type: val.type.substr(0, val.type.length - 2), - id: isNumber(id) ? int(id) : id, - val -}); - -// prettier-ignore -export function indexMat(m: Sym, id: number): Index; -// prettier-ignore -export function indexMat(m: Sym, a: number, b: number): Index<"float">; -export function indexMat(m: Sym, a: number, b?: number): Index { - const idx: any = { - tag: "idx", - type: m.type.replace("mat", "vec"), - id: int(a), - val: m - }; - return b !== undefined - ? { tag: "idx", type: "float", id: int(b), val: idx } - : idx; -} - -export const assign = ( - l: Assignable, - r: Term -): Assign => { - assert( - l.tag !== "swizzle" || (>l).val.tag === "sym", - "can't assign to non-symbol swizzle" - ); - return { - tag: "assign", - type: l.type, - l, - r - }; -}; - -const $vec = (xs: any[], init = FLOAT0) => [ - xs[0] === undefined ? init : wrapFloat(xs[0]), - ...xs.slice(1).map(wrapFloat) -]; - -const $ivec = (xs: any[], init = INT0) => [ - xs[0] === undefined ? init : wrapInt(xs[0]), - ...xs.slice(1).map(wrapInt) -]; - -const $uvec = (xs: any[], init = UINT0) => [ - xs[0] === undefined ? init : wrapUint(xs[0]), - ...xs.slice(1).map(wrapUint) -]; - -const $bvec = (xs: any[], init = FALSE) => [ - xs[0] === undefined ? init : wrapBool(xs[0]), - ...xs.slice(1).map(wrapBool) -]; - -const $mat = (xs: any[], init = FLOAT0) => [ - xs[0] === undefined ? init : wrapFloat(xs[0]), - ...xs.slice(1).map(wrapInt) -]; - -export function vec2(): Lit<"vec2">; -export function vec2(x: NumericF): Lit<"vec2">; -// export function vec2(x: Term): Lit<"vec2">; -// prettier-ignore -export function vec2(x: NumericF, y: NumericF): Lit<"vec2">; -// prettier-ignore -export function vec2(...xs: any[]): Lit<"vec2"> { - xs = $vec(xs); - return lit("vec2", xs, ["n","n"][xs.length]); -} - -export function vec3(): Lit<"vec3">; -export function vec3(x: NumericF): Lit<"vec3">; -export function vec3(x: Vec2Term, y: NumericF): Lit<"vec3">; -// prettier-ignore -export function vec3(x: NumericF, y: NumericF, z: NumericF): Lit<"vec3">; -export function vec3(...xs: any[]): Lit<"vec3"> { - xs = $vec(xs); - return lit("vec3", xs, ["n", "n", "vn"][xs.length]); -} - -export function vec4(): Lit<"vec4">; -export function vec4(x: NumericF): Lit<"vec4">; -export function vec4(x: Vec3Term, y: NumericF): Lit<"vec4">; -export function vec4(x: Vec2Term, y: Vec2Term): Lit<"vec4">; -// prettier-ignore -export function vec4(x: Vec2Term, y: NumericF, z: NumericF): Lit<"vec4">; -// prettier-ignore -export function vec4(x: NumericF, y: NumericF, z: NumericF, w: NumericF): Lit<"vec4">; -export function vec4(...xs: any[]): Lit<"vec4"> { - xs = $vec(xs); - return lit( - "vec4", - xs, - xs.length === 2 - ? isVec(xs[1]) - ? "vv" - : "vn" - : ["n", "n", , "vnn"][xs.length] - ); -} - -export function ivec2(): Lit<"ivec2">; -export function ivec2(x: NumericI): Lit<"ivec2">; -// prettier-ignore -export function ivec2(x: NumericI, y: NumericI): Lit<"ivec2">; -// prettier-ignore -export function ivec2(...xs: any[]): Lit<"ivec2"> { - return lit("ivec2", $ivec(xs), ["n","n"][xs.length]); -} - -export function ivec3(): Lit<"ivec3">; -export function ivec3(x: NumericI): Lit<"ivec3">; -export function ivec3(x: Vec2Term, y: NumericI): Lit<"ivec3">; -// prettier-ignore -export function ivec3(x: NumericI, y: NumericI, z: NumericI): Lit<"ivec3">; -export function ivec3(...xs: any[]): Lit<"ivec3"> { - return lit("ivec3", (xs = $ivec(xs)), ["n", "n", "vn"][xs.length]); -} - -export function ivec4(): Lit<"ivec4">; -export function ivec4(x: NumericI): Lit<"ivec4">; -export function ivec4(x: Vec3Term, y: NumericI): Lit<"ivec4">; -export function ivec4(x: Vec2Term, y: Vec2Term): Lit<"ivec4">; -// prettier-ignore -export function ivec4(x: Vec2Term, y: NumericI, z: NumericI): Lit<"ivec4">; -// prettier-ignore -export function ivec4(x: NumericI, y: NumericI, z: NumericI, w: NumericI): Lit<"ivec4">; -export function ivec4(...xs: any[]): Lit<"ivec4"> { - return lit( - "ivec4", - (xs = $ivec(xs)), - xs.length === 2 - ? isVec(xs[1]) - ? "vv" - : "vn" - : ["n", "n", , "vnn"][xs.length] - ); -} - -export function uvec2(): Lit<"uvec2">; -export function uvec2(x: NumericU): Lit<"uvec2">; -// prettier-ignore -export function uvec2(x: NumericU, y: NumericU): Lit<"uvec2">; -// prettier-ignore -export function uvec2(...xs: any[]): Lit<"uvec2"> { - return lit("uvec2", $uvec(xs), ["n","n"][xs.length]); -} - -export function uvec3(): Lit<"uvec3">; -export function uvec3(x: NumericU): Lit<"uvec3">; -export function uvec3(x: Vec2Term, y: NumericU): Lit<"uvec3">; -// prettier-ignore -export function uvec3(x: NumericU, y: NumericU, z: NumericU): Lit<"uvec3">; -export function uvec3(...xs: any[]): Lit<"uvec3"> { - return lit("uvec3", (xs = $uvec(xs)), ["n", "n", "vn"][xs.length]); -} - -export function uvec4(): Lit<"uvec4">; -export function uvec4(x: NumericU): Lit<"uvec4">; -export function uvec4(x: Vec3Term, y: NumericU): Lit<"uvec4">; -export function uvec4(x: Vec2Term, y: Vec2Term): Lit<"uvec4">; -// prettier-ignore -export function uvec4(x: Vec2Term, y: NumericU, z: NumericU): Lit<"uvec4">; -// prettier-ignore -export function uvec4(x: NumericU, y: NumericU, z: NumericU, w: NumericU): Lit<"uvec4">; -export function uvec4(...xs: any[]): Lit<"uvec4"> { - return lit( - "uvec4", - (xs = $uvec(xs)), - xs.length === 2 - ? isVec(xs[1]) - ? "vv" - : "vn" - : ["n", "n", , "vnn"][xs.length] - ); -} - -export function bvec2(): Lit<"bvec2">; -export function bvec2(x: NumericB): Lit<"bvec2">; -// prettier-ignore -export function bvec2(x: NumericB, y: NumericB): Lit<"bvec2">; -// prettier-ignore -export function bvec2(...xs: any[]): Lit<"bvec2"> { - return lit("bvec2", $bvec(xs), ["n","n"][xs.length]); -} - -export function bvec3(): Lit<"bvec3">; -export function bvec3(x: NumericB): Lit<"bvec3">; -export function bvec3(x: Vec2Term, y: NumericB): Lit<"bvec3">; -// prettier-ignore -export function bvec3(x: NumericB, y: NumericB, z: NumericB): Lit<"bvec3">; -export function bvec3(...xs: any[]): Lit<"bvec3"> { - return lit("bvec3", (xs = $bvec(xs)), ["n", "n", "vn"][xs.length]); -} - -export function bvec4(): Lit<"bvec4">; -export function bvec4(x: NumericB): Lit<"bvec4">; -export function bvec4(x: Vec3Term, y: NumericB): Lit<"bvec4">; -export function bvec4(x: Vec2Term, y: Vec2Term): Lit<"bvec4">; -// prettier-ignore -export function bvec4(x: Vec2Term, y: NumericB, z: NumericB): Lit<"bvec4">; -// prettier-ignore -export function bvec4(x: NumericB, y: NumericB, z: NumericB, w: NumericB): Lit<"bvec4">; -export function bvec4(...xs: any[]): Lit<"bvec4"> { - return lit( - "bvec4", - (xs = $bvec(xs)), - xs.length === 2 - ? isVec(xs[1]) - ? "vv" - : "vn" - : ["n", "n", , "vnn"][xs.length] - ); -} - -export function mat2(): Lit<"mat2">; -export function mat2(x: NumericF): Lit<"mat2">; -export function mat2(x: Vec2Term, y: Vec2Term): Lit<"mat2">; -// prettier-ignore -export function mat2(a: NumericF, b: NumericF, c: NumericF, d: NumericF): Lit<"mat2">; -export function mat2(...xs: any[]): Lit<"mat2"> { - return lit("mat2", (xs = $mat(xs, FLOAT1)), ["n", "n", "vv"][xs.length]); -} - -export function mat3(): Lit<"mat3">; -export function mat3(x: NumericF): Lit<"mat3">; -// prettier-ignore -export function mat3(x: Vec3Term, y: Vec3Term, z: Vec3Term): Lit<"mat3">; -// prettier-ignore -export function mat3(a: NumericF, b: NumericF, c: NumericF, d: NumericF, e: NumericF, f: NumericF, g: NumericF, h: NumericF, i: NumericF): Lit<"mat3">; -export function mat3(...xs: any[]): Lit<"mat3"> { - return lit("mat3", (xs = $mat(xs, FLOAT1)), ["n", "n", , "vvv"][xs.length]); -} - -export function mat4(): Lit<"mat4">; -export function mat4(x: NumericF): Lit<"mat4">; -// prettier-ignore -export function mat4(x: Vec4Term, y: Vec4Term, z: Vec4Term, w: Vec4Term): Lit<"mat4">; -// prettier-ignore -export function mat4(a: NumericF, b: NumericF, c: NumericF, d: NumericF, e: NumericF, f: NumericF, g: NumericF, h: NumericF, i: NumericF, j: NumericF, k: NumericF, l: NumericF, m: NumericF, n: NumericF, o: NumericF, p: NumericF): Lit<"mat4">; -export function mat4(...xs: any[]): Lit<"mat4"> { - return lit( - "mat4", - (xs = $mat(xs, FLOAT1)), - ["n", "n", , , "vvvv"][xs.length] - ); -} - -export const op1 = ( - op: Operator, - val: Term, - post = false -): Op1 => ({ - tag: "op1", - type: val.type, - op, - val, - post -}); - -const OP_INFO: IObjectOf = { - mave: "mv", - vema: "vm", - vefl: "vn", - mafl: "vn", - flve: "nv", - flma: "nv", - ivin: "vn", - iniv: "nv", - uvui: "vn", - uiuv: "nv" -}; - -export const op2 = ( - op: Operator, - _l: Term | number, - _r: Term | number, - rtype?: Type, - info?: string -): Op2 => { - const nl = isNumber(_l); - const nr = isNumber(_r); - let type: Type; - let l: Term; - let r: Term; - if (nl) { - if (nr) { - // (number, number) - l = float(_l); - r = float(_r); - type = "float"; - } else { - // (number, term) - r = >_r; - l = numberWithMatchingType(r, _l); - type = r.type; - } - } else if (nr) { - // (term, number) - l = >_l; - r = numberWithMatchingType(l, _r); - type = l.type; - } else { - // (term, term) - l = >_l; - r = >_r; - type = - rtype || - (isVec(l) - ? l.type - : isVec(r) - ? r.type - : isMat(r) - ? r.type - : l.type); - } - return { - tag: "op2", - type: rtype || type!, - info: info || OP_INFO[l!.type.substr(0, 2) + r!.type.substr(0, 2)], - op, - l: l!, - r: r! - }; -}; - -export const inc = (t: Sym): Op1 => - op1("++", t, true); - -export const dec = (t: Sym): Op1 => - op1("--", t, true); - -// prettier-ignore -export function add(l: Term, r: Term): Op2; -export function add(l: number, r: Term): Op2; -export function add(l: Term, r: number): Op2; -// prettier-ignore -export function add(l: FloatTerm | number, r: Term): Op2; -// prettier-ignore -export function add(l: Term, r: FloatTerm | number): Op2; -// prettier-ignore -export function add(l: IntTerm | number, r: Term): Op2; -// prettier-ignore -export function add(l: Term, r: IntTerm | number): Op2; -// prettier-ignore -export function add(l: Term | number, r: Term | number): Op2 { - return op2("+", l, r); -} - -// prettier-ignore -export function sub(l: Term, r: Term): Op2; -export function sub(l: number, r: Term): Op2; -export function sub(l: Term, r: number): Op2; -// prettier-ignore -export function sub(l: FloatTerm | number, r: Term): Op2; -// prettier-ignore -export function sub(l: Term, r: FloatTerm | number): Op2; -// prettier-ignore -export function sub(l: IntTerm | number, r: Term): Op2; -// prettier-ignore -export function sub(l: Term, r: IntTerm| number): Op2; -export function sub(l: Term | number, r: Term | number): Op2 { - return op2("-", l, r); -} - -// prettier-ignore -export function mul(l: Term, r: Term): Op2; -export function mul(l: number, r: Term): Op2; -export function mul(l: Term, r: number): Op2; -// prettier-ignore -export function mul(l: FloatTerm | number, r: Term): Op2; -// prettier-ignore -export function mul(l: Term, r: FloatTerm | number): Op2; -// prettier-ignore -export function mul(l: IntTerm | number, r: Term): Op2; -// prettier-ignore -export function mul(l: Term, r: IntTerm | number): Op2; -export function mul(l: Mat2Term, r: Vec2Term): Op2<"vec2">; -export function mul(l: Mat3Term, r: Vec3Term): Op2<"vec3">; -export function mul(l: Mat4Term, r: Vec4Term): Op2<"vec4">; -export function mul(l: Vec2Term, r: Mat2Term): Op2<"vec2">; -export function mul(l: Vec3Term, r: Mat3Term): Op2<"vec3">; -export function mul(l: Vec4Term, r: Mat4Term): Op2<"vec4">; -export function mul(l: Term | number, r: Term | number): Op2 { - return op2( - "*", - l, - r, - !isNumber(l) && !isNumber(r) && isMat(l) && isVec(r) - ? r.type - : undefined - ); -} - -// prettier-ignore -export function div(l: Term, r: Term): Op2; -export function div(l: number, r: Term): Op2; -export function div(l: Term, r: number): Op2; -// prettier-ignore -export function div(l: FloatTerm | number, r: Term): Op2; -// prettier-ignore -export function div(l: Term, r: FloatTerm | number): Op2; -// prettier-ignore -export function div(l: IntTerm | number, r: Term): Op2; -// prettier-ignore -export function div(l: Term, r: IntTerm | number): Op2; -export function div(l: Term | number, r: Term | number): Op2 { - return op2("/", l, r); -} - -/** - * Integer % (modulo) operator - * - * @param l - * @param b - */ -// prettier-ignore -export function modi(l: Term, r: Term): Op2; -// prettier-ignore -export function modi(l: IntTerm | number, r: Term): Op2; -// prettier-ignore -export function modi(l: Term, r: IntTerm | number): Op2; -// prettier-ignore -export function modi(l: UintTerm | number, r: Term): Op2; -// prettier-ignore -export function modi(l: Term, r: UintTerm | number): Op2; -export function modi(l: Term | number, r: Term | number): Op2 { - return op2( - "%", - isNumber(l) ? numberWithMatchingType(>r, l) : l, - isNumber(r) ? numberWithMatchingType(>l, r) : r - ); -} - -export const neg = (val: Term) => - op1("-", val); - -/** - * Multiply-add: a * b + c. All must be of same type. - * - * @param a - * @param b - * @param c - */ -export const madd = < - A extends Prim | IVec | UVec | "int" | "uint", - B extends A, - C extends B ->( - a: Term, - b: Term, - c: Term -): Term => add(mul(>a, b), c); - -/** - * Add-multiply: (a + b) * c. All must be of same type. - * - * @param a - * @param b - * @param c - */ -export const addm = ( - a: Term, - b: Term, - c: Term -): Term => mul(add(>a, b), c); - -export const not = (val: BoolTerm) => op1("!", val); -export const or = (a: BoolTerm, b: BoolTerm) => op2("||", a, b); -export const and = (a: BoolTerm, b: BoolTerm) => op2("&&", a, b); - -const cmp = (op: ComparisonOperator) => ( - a: Term, - b: Term -): BoolTerm => op2(op, a, b, "bool"); - -export const eq = cmp("=="); -export const neq = cmp("!="); -export const lt = cmp("<"); -export const lte = cmp("<="); -export const gt = cmp(">"); -export const gte = cmp(">="); - -export const bitnot = | Term>( - val: T -) => op1("~", val); - -// prettier-ignore -export function bitand(l: Term, r: Term): Term; -// prettier-ignore -export function bitand(l: Term, r: IntTerm | number): Term; -// prettier-ignore -export function bitand(l: IntTerm | number, r: Term): Term; -// prettier-ignore -export function bitand(l: Term, r: UintTerm | number): Term; -// prettier-ignore -export function bitand(l: UintTerm | number, r: Term): Term; -// prettier-ignore -export function bitand(l: Term | number, r: Term | number): Op2 { - return op2("&", l, r, undefined); -} - -// prettier-ignore -export function bitor(l: Term, r: Term): Term; -// prettier-ignore -export function bitor(l: Term, r: IntTerm | number): Term; -// prettier-ignore -export function bitor(l: IntTerm | number, r: Term): Term; -// prettier-ignore -export function bitor(l: Term, r: UintTerm | number): Term; -// prettier-ignore -export function bitor(l: UintTerm | number, r: Term): Term; -// prettier-ignore -export function bitor(l: Term | number, r: Term | number): Op2 { - return op2("|", l, r, undefined); -} - -// prettier-ignore -export function bitxor(l: Term, r: Term): Term; -// prettier-ignore -export function bitxor(l: Term, r: IntTerm | number): Term; -// prettier-ignore -export function bitxor(l: IntTerm | number, r: Term): Term; -// prettier-ignore -export function bitxor(l: Term, r: UintTerm | number): Term; -// prettier-ignore -export function bitxor(l: UintTerm | number, r: Term): Term; -// prettier-ignore -export function bitxor(l: Term | number, r: Term | number): Op2 { - return op2("^", l, r, undefined); -} - -/** - * Wraps the given AST node array in `scope` node, optionally as global - * scope (default false). The interpretation of the global flag is - * dependent on the target code gen. I.e. for GLSL / JS, the flag - * disables wrapping the scope's body in `{}`, but else has no - * difference. In general this node type only serves as internal - * mechanism for various control flow AST nodes and should not need to - * be used directly from user land code (though might be useful to - * create custom / higher level control flow nodes). - * - * @param body - * @param global - */ -export const scope = (body: (Term | null)[], global = false): Scope => ({ - tag: "scope", - type: "void", - body: []>( - body - .filter((x) => x != null) - .map((x) => (x!.tag === "sym" ? decl(>x) : x)) - ), - global -}); - -/** - * Takes an array of global sym/var definitions (`input()`, `output()`, - * `uniform()`) and functions defined via `defn()`. Constructs the call - * graph of all transitively used functions and bundles everything in - * topological order within a global scope object, which is then - * returned to the user and can be passed to a target codegen for full - * program output. - * - * @see scope - * @see input - * @see output - * @see uniform - * - * @param body - */ -export const program = (body: (Sym | Func)[]) => { - const syms = body.filter((x) => x.tag !== "fn"); - const g = body.reduce( - (acc, x) => (x.tag === "fn" ? buildCallGraph(>x, acc) : acc), - new DGraph>() - ); - return scope(syms.concat(g.sort()), true); -}; - -const defArg = (a: Arg): FuncArg => { - const [type, id, opts] = isString(a) ? <[T, string?, SymOpts?]>[a] : a; - return { - tag: "arg", - type, - id: id || gensym(), - opts: { q: "in", ...opts } - }; -}; - -/** - * Defines a new function with up to 8 typed checked arguments. - * - * @param type return type - * @param name function name - * @param args arg types / names / opts - * @param body function body closure - * @param deps array of userland functions called from this function - */ -// prettier-ignore -export function defn(type: T, name: string, args: [], body: FnBody0): TaggedFn0; -// prettier-ignore -export function defn(type: T, name: string, args: Arg1, body: FnBody1): TaggedFn1; -// prettier-ignore -export function defn(type: T, name: string, args: Arg2, body: FnBody2): TaggedFn2; -// prettier-ignore -export function defn(type: T, name: string, args: Arg3, body: FnBody3): TaggedFn3; -// prettier-ignore -export function defn(type: T, name: string, args: Arg4, body: FnBody4): TaggedFn4; -// prettier-ignore -export function defn(type: T, name: string, args: Arg5, body: FnBody5): TaggedFn5; -// prettier-ignore -export function defn(type: T, name: string, args: Arg6, body: FnBody6): TaggedFn6; -// prettier-ignore -export function defn(type: T, name: string, args: Arg7, body: FnBody7): TaggedFn7; -// prettier-ignore -export function defn(type: T, name: string, args: Arg8, body: FnBody8): TaggedFn8; -// prettier-ignore -export function defn(type: Type, id: string, _args: Arg[], _body: (...xs: Sym[]) => ScopeBody): Func { - const args = _args.map(defArg); - const body = []>( - _body(...args.map((x) => sym(x.type, x.id, x.opts))).filter( - (x) => x != null - ) - ); - // count & check returns - const returns = walk( - (n, t) => { - if (t.tag === "ret") { - assert( - t.type === type, - `wrong return type for function '${id}', expected ${type}, got ${ - t.type - }` - ); - n++; - } - return n; - }, - scopedChildren, - 0, - body - ); - if (type !== "void" && !returns) { - throw new Error(`function '${id}' must return a value of type ${type}`); - } - // verify all non-builtin functions called are also - // provided as deps to ensure complete call graph later - const deps = walk( - (acc, t) => { - if (t.tag === "call" && (>t).fn) { - acc.push((>t).fn!); - } - return acc; - }, - allChildren, - []>[], - body - ); - const $: any = (...xs: any[]) => (funcall)($, ...xs); - return Object.assign($, >{ - tag: "fn", - type, - id, - args, - deps, - scope: scope(body) - }); -} - -/** - * Syntax sugar for defining `void main()` functions. - * - * @param body - */ -export const defMain = (body: FnBody0) => defn("void", "main", [], body); - -export function ret(): FuncReturn<"void">; -export function ret(val: Term): FuncReturn; -export function ret(val?: Term): FuncReturn { - return { - tag: "ret", - type: val ? val.type : "void", - val - }; -} - -// prettier-ignore -export function funcall(fn: string, type: T, ...args: Term[]): FnCall; -export function funcall(fn: TaggedFn0): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn1, a: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn2, a: Term, b: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn3, a: Term, b: Term, c: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn4, a: Term, b: Term, c: Term, d: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn5, a: Term, b: Term, c: Term, d: Term, e: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn6, a: Term, b: Term, c: Term, d: Term, e: Term, f: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn7, a: Term, b: Term, c: Term, d: Term, e: Term, f: Term, g: Term): FnCall; -// prettier-ignore -export function funcall(fn: TaggedFn8, a: Term, b: Term, c: Term, d: Term, e: Term, f: Term, g: Term, h: Term): FnCall; -// prettier-ignore -export function funcall(fn: string | Func, ...args: Term[]): FnCall { - return isString(fn) - ? { - tag: "call", - type: args[0], - id: fn, - args: args.slice(1) - } - : { - tag: "call", - type: fn.type, - id: fn.id, - args, - fn - }; -} - -export const builtinCall = ( - id: string, - type: T, - ...args: Term[] -): FnCall => ({ - tag: "call_i", - type, - id, - args -}); - -export const ifThen = ( - test: BoolTerm, - truthy: Term[], - falsey?: Term[] -): Branch => ({ - tag: "if", - type: "void", - test, - t: scope(truthy), - f: falsey ? scope(falsey) : undefined -}); - -export const ternary = ( - test: BoolTerm, - t: Term, - f: Term -): Ternary => ({ - tag: "ternary", - type: t.type, - test, - t, - f -}); - -// prettier-ignore -export function forLoop(test: Fn, BoolTerm>, body: FnBody1): ForLoop; -// prettier-ignore -export function forLoop(init: Sym | undefined, test: Fn, BoolTerm>, body: FnBody1): ForLoop; -// prettier-ignore -export function forLoop(init: Sym | undefined, test: Fn, BoolTerm>, iter: Fn, Term>, body: FnBody1): ForLoop; -export function forLoop(...xs: any[]): ForLoop { - const [init, test, iter, body] = - xs.length === 2 - ? [, xs[0], , xs[1]] - : xs.length === 3 - ? [xs[0], xs[1], , xs[2]] - : xs; - return { - tag: "for", - type: "void", - init: init ? decl(init) : undefined, - test: test(init!), - iter: iter ? iter(init!) : undefined, - scope: scope(body(init!)) - }; -} - -export const whileLoop = (test: BoolTerm, body: Term[]): WhileLoop => ({ - tag: "while", - type: "void", - test, - scope: scope(body) -}); - -const ctrl = (id: string): ControlFlow => ({ - tag: "ctrl", - type: "void", - id -}); - -export const brk = ctrl("break"); - -export const cont = ctrl("continue"); - -export const discard = ctrl("discard"); diff --git a/packages/shader-ast/src/ast/assign.ts b/packages/shader-ast/src/ast/assign.ts new file mode 100644 index 0000000000..966d6798be --- /dev/null +++ b/packages/shader-ast/src/ast/assign.ts @@ -0,0 +1,19 @@ +import { assert } from "@thi.ng/api"; +import { Assign, Swizzle, Term } from "../api/nodes"; +import { Assignable, Type } from "../api/types"; + +export const assign = ( + l: Assignable, + r: Term +): Assign => { + assert( + l.tag !== "swizzle" || (>l).val.tag === "sym", + "can't assign to non-symbol swizzle" + ); + return { + tag: "assign", + type: l.type, + l, + r + }; +}; diff --git a/packages/shader-ast/src/ast/checks.ts b/packages/shader-ast/src/ast/checks.ts new file mode 100644 index 0000000000..e6983b20af --- /dev/null +++ b/packages/shader-ast/src/ast/checks.ts @@ -0,0 +1,55 @@ +import { Term } from "../api/nodes"; + +const RE_VEC = /^[iub]?vec[234]$/; +const RE_MAT = /^mat[234]$/; + +/** + * Returns true, if given term evaluates to a boolean value. + */ +export const isBool = (t: Term) => t.type === "bool"; + +/** + * Returns true, if given term evaluates to a float value. + */ +export const isFloat = (t: Term) => t.type === "float"; + +/** + * Returns true, if given term evaluates to a signed integer value. + */ +export const isInt = (t: Term) => t.type === "int"; + +/** + * Returns true, if given term evaluates to an unsigned integer value. + */ +export const isUint = (t: Term) => t.type === "uint"; + +/** + * Returns true, if given term is a literal. + */ +export const isLit = (t: Term) => t.tag === "lit"; + +/** + * Returns true, if given term is a float literal. + */ +export const isLitFloat = (t: Term) => isLit(t) && isFloat(t); + +/** + * Returns true, if given term is a signed integer literal. + */ +export const isLitInt = (t: Term) => isLit(t) && isInt(t); + +/** + * Returns true, if given term is a numeric literal (float, int, uint). + */ +export const isLitNumeric = (t: Term) => + isLit(t) && (isFloat(t) || isInt(t) || isUint(t)); + +/** + * Returns true, if given term evaluates to a vector value (vec, ivec, bvec). + */ +export const isVec = (t: Term) => RE_VEC.test(t.type); + +/** + * Returns true, if given term evaluates to a matrix value. + */ +export const isMat = (t: Term) => RE_MAT.test(t.type); diff --git a/packages/shader-ast/src/ast/controlflow.ts b/packages/shader-ast/src/ast/controlflow.ts new file mode 100644 index 0000000000..44c2e2645e --- /dev/null +++ b/packages/shader-ast/src/ast/controlflow.ts @@ -0,0 +1,80 @@ +import { FnBody1 } from ".."; +import { Fn } from "@thi.ng/api"; +import { + Branch, + ControlFlow, + ForLoop, + Sym, + Term, + Ternary, + WhileLoop +} from "../api/nodes"; +import { BoolTerm } from "../api/terms"; +import { Type } from "../api/types"; +import { decl, scope } from "./scope"; + +export const ifThen = ( + test: BoolTerm, + truthy: Term[], + falsey?: Term[] +): Branch => ({ + tag: "if", + type: "void", + test, + t: scope(truthy), + f: falsey ? scope(falsey) : undefined +}); + +export const ternary = ( + test: BoolTerm, + t: Term, + f: Term +): Ternary => ({ + tag: "ternary", + type: t.type, + test, + t, + f +}); + +// prettier-ignore +export function forLoop(test: Fn, BoolTerm>, body: FnBody1): ForLoop; +// prettier-ignore +export function forLoop(init: Sym | undefined, test: Fn, BoolTerm>, body: FnBody1): ForLoop; +// prettier-ignore +export function forLoop(init: Sym | undefined, test: Fn, BoolTerm>, iter: Fn, Term>, body: FnBody1): ForLoop; +export function forLoop(...xs: any[]): ForLoop { + const [init, test, iter, body] = + xs.length === 2 + ? [, xs[0], , xs[1]] + : xs.length === 3 + ? [xs[0], xs[1], , xs[2]] + : xs; + return { + tag: "for", + type: "void", + init: init ? decl(init) : undefined, + test: test(init!), + iter: iter ? iter(init!) : undefined, + scope: scope(body(init!)) + }; +} + +export const whileLoop = (test: BoolTerm, body: Term[]): WhileLoop => ({ + tag: "while", + type: "void", + test, + scope: scope(body) +}); + +const ctrl = (id: string): ControlFlow => ({ + tag: "ctrl", + type: "void", + id +}); + +export const brk = ctrl("break"); + +export const cont = ctrl("continue"); + +export const discard = ctrl("discard"); diff --git a/packages/shader-ast/src/ast/function.ts b/packages/shader-ast/src/ast/function.ts new file mode 100644 index 0000000000..9db08fffa9 --- /dev/null +++ b/packages/shader-ast/src/ast/function.ts @@ -0,0 +1,205 @@ +import { assert } from "@thi.ng/api"; +import { isString } from "@thi.ng/checks"; +import { + Arg, + Arg1, + Arg2, + Arg3, + Arg4, + Arg5, + Arg6, + Arg7, + Arg8, + FnBody0, + FnBody1, + FnBody2, + FnBody3, + FnBody4, + FnBody5, + FnBody6, + FnBody7, + FnBody8, + ScopeBody +} from "../api/function"; +import { + FnCall, + Func, + FuncArg, + FuncReturn, + Sym, + TaggedFn0, + TaggedFn1, + TaggedFn2, + TaggedFn3, + TaggedFn4, + TaggedFn5, + TaggedFn6, + TaggedFn7, + TaggedFn8, + Term +} from "../api/nodes"; +import { SymOpts } from "../api/syms"; +import { Type } from "../api/types"; +import { gensym } from "./idgen"; +import { + allChildren, + scope, + scopedChildren, + walk +} from "./scope"; +import { sym } from "./sym"; + +const defArg = (a: Arg): FuncArg => { + const [type, id, opts] = isString(a) ? <[T, string?, SymOpts?]>[a] : a; + return { + tag: "arg", + type, + id: id || gensym(), + opts: { q: "in", ...opts } + }; +}; + +/** + * Defines a new function with up to 8 typed checked arguments. + * + * @param type return type + * @param name function name + * @param args arg types / names / opts + * @param body function body closure + * @param deps array of userland functions called from this function + */ +// prettier-ignore +export function defn(type: T, name: string, args: [], body: FnBody0): TaggedFn0; +// prettier-ignore +export function defn(type: T, name: string, args: Arg1, body: FnBody1): TaggedFn1; +// prettier-ignore +export function defn(type: T, name: string, args: Arg2, body: FnBody2): TaggedFn2; +// prettier-ignore +export function defn(type: T, name: string, args: Arg3, body: FnBody3): TaggedFn3; +// prettier-ignore +export function defn(type: T, name: string, args: Arg4, body: FnBody4): TaggedFn4; +// prettier-ignore +export function defn(type: T, name: string, args: Arg5, body: FnBody5): TaggedFn5; +// prettier-ignore +export function defn(type: T, name: string, args: Arg6, body: FnBody6): TaggedFn6; +// prettier-ignore +export function defn(type: T, name: string, args: Arg7, body: FnBody7): TaggedFn7; +// prettier-ignore +export function defn(type: T, name: string, args: Arg8, body: FnBody8): TaggedFn8; +// prettier-ignore +export function defn(type: Type, id: string, _args: Arg[], _body: (...xs: Sym[]) => ScopeBody): Func { + const args = _args.map(defArg); + const body = []>( + _body(...args.map((x) => sym(x.type, x.id, x.opts))).filter( + (x) => x != null + ) + ); + // count & check returns + const returns = walk( + (n, t) => { + if (t.tag === "ret") { + assert( + t.type === type, + `wrong return type for function '${id}', expected ${type}, got ${ + t.type + }` + ); + n++; + } + return n; + }, + scopedChildren, + 0, + body + ); + if (type !== "void" && !returns) { + throw new Error(`function '${id}' must return a value of type ${type}`); + } + // verify all non-builtin functions called are also + // provided as deps to ensure complete call graph later + const deps = walk( + (acc, t) => { + if (t.tag === "call" && (>t).fn) { + acc.push((>t).fn!); + } + return acc; + }, + allChildren, + []>[], + body + ); + const $: any = (...xs: any[]) => (funcall)($, ...xs); + return Object.assign($, >{ + tag: "fn", + type, + id, + args, + deps, + scope: scope(body) + }); +} + +/** + * Syntax sugar for defining `void main()` functions. + * + * @param body + */ +export const defMain = (body: FnBody0) => defn("void", "main", [], body); + +export function ret(): FuncReturn<"void">; +export function ret(val: Term): FuncReturn; +export function ret(val?: Term): FuncReturn { + return { + tag: "ret", + type: val ? val.type : "void", + val + }; +} + +// prettier-ignore +export function funcall(fn: string, type: T, ...args: Term[]): FnCall; +export function funcall(fn: TaggedFn0): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn1, a: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn2, a: Term, b: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn3, a: Term, b: Term, c: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn4, a: Term, b: Term, c: Term, d: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn5, a: Term, b: Term, c: Term, d: Term, e: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn6, a: Term, b: Term, c: Term, d: Term, e: Term, f: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn7, a: Term, b: Term, c: Term, d: Term, e: Term, f: Term, g: Term): FnCall; +// prettier-ignore +export function funcall(fn: TaggedFn8, a: Term, b: Term, c: Term, d: Term, e: Term, f: Term, g: Term, h: Term): FnCall; +// prettier-ignore +export function funcall(fn: string | Func, ...args: Term[]): FnCall { + return isString(fn) + ? { + tag: "call", + type: args[0], + id: fn, + args: args.slice(1) + } + : { + tag: "call", + type: fn.type, + id: fn.id, + args, + fn + }; +} + +export const builtinCall = ( + id: string, + type: T, + ...args: Term[] +): FnCall => ({ + tag: "call_i", + type, + id, + args +}); diff --git a/packages/shader-ast/src/ast/idgen.ts b/packages/shader-ast/src/ast/idgen.ts new file mode 100644 index 0000000000..cb0435f409 --- /dev/null +++ b/packages/shader-ast/src/ast/idgen.ts @@ -0,0 +1,13 @@ +let symID = 0; + +/** + * Helper for deterministic code generation / testing. Resets sym ID + * counter. + */ +export const resetSymID = () => (symID = 0); + +/** + * Generates a new symbol name, e.g. `_sa2`. Uses base36 for counter to + * keep names short. + */ +export const gensym = () => `_s${(symID++).toString(36)}`; diff --git a/packages/shader-ast/src/ast/indexed.ts b/packages/shader-ast/src/ast/indexed.ts new file mode 100644 index 0000000000..3ec2f94da1 --- /dev/null +++ b/packages/shader-ast/src/ast/indexed.ts @@ -0,0 +1,36 @@ +import { isNumber } from "@thi.ng/checks"; +import { Index, Sym } from "../api/nodes"; +import { UintTerm } from "../api/terms"; +import { + Indexable, + IndexTypeMap, + MatIndexTypeMap, + NumericI +} from "../api/types"; +import { int } from "./lit"; + +export const index = ( + val: Sym, + id: NumericI | UintTerm +): Index => ({ + tag: "idx", + type: val.type.substr(0, val.type.length - 2), + id: isNumber(id) ? int(id) : id, + val +}); + +// prettier-ignore +export function indexMat(m: Sym, id: number): Index; +// prettier-ignore +export function indexMat(m: Sym, a: number, b: number): Index<"float">; +export function indexMat(m: Sym, a: number, b?: number): Index { + const idx: any = { + tag: "idx", + type: m.type.replace("mat", "vec"), + id: int(a), + val: m + }; + return b !== undefined + ? { tag: "idx", type: "float", id: int(b), val: idx } + : idx; +} diff --git a/packages/shader-ast/src/ast/item.ts b/packages/shader-ast/src/ast/item.ts new file mode 100644 index 0000000000..89ba26ee88 --- /dev/null +++ b/packages/shader-ast/src/ast/item.ts @@ -0,0 +1,72 @@ +import { IObjectOf } from "@thi.ng/api"; +import { Term } from "../api/nodes"; +import { FloatTerm } from "../api/terms"; +import { + Int, + IVec, + Prim, + Type, + UVec +} from "../api/types"; +import { + bool, + float, + int, + uint, + vec2, + vec3, + vec4 +} from "./lit"; + +/** + * Returns base type for given term. Used for array ops. + * + * ``` + * itemType("vec2[]") => "vec2" + * ``` + */ +export const itemType = (type: Type) => type.replace("[]", ""); + +/** + * Takes a numeric term and a plain number, returns number wrapped in + * typed literal compatible with term. + * + * @param t + * @param x + */ +export const numberWithMatchingType = (t: Term, x: number) => { + const id = t.type[0]; + return id === "i" + ? int(x) + : id === "u" + ? uint(x) + : id === "b" + ? bool(x) + : float(x); +}; + +export const matchingPrimFor = ( + t: Term, + x: FloatTerm +): Term => { + const ctor = ({ vec2, vec3, vec4 })[t.type]; + return ctor ? ctor(x) : x; +}; + +export const matchingBoolType = ( + t: Term +) => + (>{ + float: "bool", + int: "bool", + uint: "bool", + vec2: "bvec2", + ivec2: "bvec2", + uvec2: "bvec2", + vec3: "bvec3", + ivec3: "bvec3", + uvec3: "bvec3", + vec4: "bvec4", + ivec4: "bvec4", + uvec4: "bvec4" + })[t.type]; diff --git a/packages/shader-ast/src/ast/lit.ts b/packages/shader-ast/src/ast/lit.ts new file mode 100644 index 0000000000..f066d04313 --- /dev/null +++ b/packages/shader-ast/src/ast/lit.ts @@ -0,0 +1,296 @@ +import { Fn } from "@thi.ng/api"; +import { isBoolean, isNumber } from "@thi.ng/checks"; +import { Lit, Term } from "../api/nodes"; +import { + FloatTerm, + IntTerm, + UintTerm, + Vec2Term, + Vec3Term, + Vec4Term +} from "../api/terms"; +import { + NumericB, + NumericF, + NumericI, + NumericU, + Type +} from "../api/types"; +import { isVec } from "./checks"; + +export const lit = ( + type: T, + val: any, + info?: string +): Lit => ({ + tag: "lit", + type, + info, + val +}); + +export const bool = (x: NumericB) => lit("bool", isNumber(x) ? !!x : x); + +export const float = (x: NumericB) => + lit("float", isBoolean(x) ? (x) & 1 : x); + +export const int = (x: NumericB) => + lit("int", isBoolean(x) ? (x) & 1 : isNumber(x) ? x | 0 : x); + +export const uint = (x: NumericB) => + lit("uint", isBoolean(x) ? (x) & 1 : isNumber(x) ? x >>> 0 : x); + +const wrap = (type: T, ctor: Fn>) => ( + x?: any +): Term | undefined => + isNumber(x) + ? ctor(x) + : x !== undefined && !isVec(x) && x.type !== type + ? ctor(x) + : x; + +/** + * Takes a plain number or numeric term and wraps it as float literal if + * needed. + * + * @param x + */ +export const wrapFloat = wrap("float", float); + +/** + * Takes a plain number or numeric term and wraps it as signed integer + * literal if needed. + * + * @param x + */ +export const wrapInt = wrap("int", int); + +/** + * Takes a plain number or numeric term and wraps it as unsigned integer + * literal if needed. + * + * @param x + */ +export const wrapUint = wrap("uint", uint); + +/** + * Takes a plain number or numeric term and wraps it as boolean literal + * if needed. + * + * @param x + */ +export const wrapBool = wrap("bool", bool); + +export const TRUE = lit("bool", true); +export const FALSE = lit("bool", false); + +export const FLOAT0: FloatTerm = float(0); +export const FLOAT1: FloatTerm = float(1); +export const FLOAT2: FloatTerm = float(2); +export const FLOAT05: FloatTerm = float(0.5); + +export const INT0: IntTerm = int(0); +export const INT1: IntTerm = int(1); + +export const UINT0: UintTerm = uint(0); +export const UINT1: UintTerm = uint(1); + +export const PI: FloatTerm = float(Math.PI); +export const TAU: FloatTerm = float(Math.PI * 2); +export const HALF_PI: FloatTerm = float(Math.PI / 2); +export const SQRT2: FloatTerm = float(Math.SQRT2); + +const $gvec = (wrap: Fn | undefined>, init: Term) => ( + xs: any[] +) => [xs[0] === undefined ? init : wrap(xs[0]), ...xs.slice(1).map(wrap)]; + +const $vec = $gvec(wrapFloat, FLOAT0); + +const $ivec = $gvec(wrapInt, INT0); + +const $uvec = $gvec(wrapUint, UINT0); + +const $bvec = $gvec(wrapBool, FALSE); + +const $gvec2 = ( + type: T, + ctor: Fn | undefined)[]>, + xs: any[] +) => lit(type, (xs = ctor(xs)), ["n", "n"][xs.length]); + +const $gvec3 = ( + type: T, + ctor: Fn | undefined)[]>, + xs: any[] +) => lit(type, (xs = ctor(xs)), ["n", "n", "vn"][xs.length]); + +const $gvec4 = ( + type: T, + ctor: Fn | undefined)[]>, + xs: any[] +) => + lit( + type, + (xs = ctor(xs)), + xs.length === 2 + ? isVec(xs[1]) + ? "vv" + : "vn" + : ["n", "n", , "vnn"][xs.length] + ); + +const $gmat = ( + type: T, + info: (string | undefined)[], + xs: any[] +) => lit(type, (xs = $vec(xs)), info[xs.length]); + +export function vec2(): Lit<"vec2">; +export function vec2(x: NumericF): Lit<"vec2">; +// export function vec2(x: Term): Lit<"vec2">; +// prettier-ignore +export function vec2(x: NumericF, y: NumericF): Lit<"vec2">; +// prettier-ignore +export function vec2(...xs: any[]): Lit<"vec2"> { + return $gvec2("vec2", $vec, xs); +} + +export function vec3(): Lit<"vec3">; +export function vec3(x: NumericF): Lit<"vec3">; +export function vec3(x: Vec2Term, y: NumericF): Lit<"vec3">; +// prettier-ignore +export function vec3(x: NumericF, y: NumericF, z: NumericF): Lit<"vec3">; +export function vec3(...xs: any[]): Lit<"vec3"> { + return $gvec3("vec3", $vec, xs); +} + +export function vec4(): Lit<"vec4">; +export function vec4(x: NumericF): Lit<"vec4">; +export function vec4(x: Vec3Term, y: NumericF): Lit<"vec4">; +export function vec4(x: Vec2Term, y: Vec2Term): Lit<"vec4">; +// prettier-ignore +export function vec4(x: Vec2Term, y: NumericF, z: NumericF): Lit<"vec4">; +// prettier-ignore +export function vec4(x: NumericF, y: NumericF, z: NumericF, w: NumericF): Lit<"vec4">; +export function vec4(...xs: any[]): Lit<"vec4"> { + return $gvec4("vec4", $vec, xs); +} + +export function ivec2(): Lit<"ivec2">; +export function ivec2(x: NumericI): Lit<"ivec2">; +// prettier-ignore +export function ivec2(x: NumericI, y: NumericI): Lit<"ivec2">; +// prettier-ignore +export function ivec2(...xs: any[]): Lit<"ivec2"> { + return $gvec2("ivec2", $ivec, xs); +} + +export function ivec3(): Lit<"ivec3">; +export function ivec3(x: NumericI): Lit<"ivec3">; +export function ivec3(x: Vec2Term, y: NumericI): Lit<"ivec3">; +// prettier-ignore +export function ivec3(x: NumericI, y: NumericI, z: NumericI): Lit<"ivec3">; +export function ivec3(...xs: any[]): Lit<"ivec3"> { + return $gvec3("ivec3", $ivec, xs); +} + +export function ivec4(): Lit<"ivec4">; +export function ivec4(x: NumericI): Lit<"ivec4">; +export function ivec4(x: Vec3Term, y: NumericI): Lit<"ivec4">; +export function ivec4(x: Vec2Term, y: Vec2Term): Lit<"ivec4">; +// prettier-ignore +export function ivec4(x: Vec2Term, y: NumericI, z: NumericI): Lit<"ivec4">; +// prettier-ignore +export function ivec4(x: NumericI, y: NumericI, z: NumericI, w: NumericI): Lit<"ivec4">; +export function ivec4(...xs: any[]): Lit<"ivec4"> { + return $gvec4("ivec4", $ivec, xs); +} + +export function uvec2(): Lit<"uvec2">; +export function uvec2(x: NumericU): Lit<"uvec2">; +// prettier-ignore +export function uvec2(x: NumericU, y: NumericU): Lit<"uvec2">; +// prettier-ignore +export function uvec2(...xs: any[]): Lit<"uvec2"> { + return $gvec2("uvec2", $uvec, xs); +} + +export function uvec3(): Lit<"uvec3">; +export function uvec3(x: NumericU): Lit<"uvec3">; +export function uvec3(x: Vec2Term, y: NumericU): Lit<"uvec3">; +// prettier-ignore +export function uvec3(x: NumericU, y: NumericU, z: NumericU): Lit<"uvec3">; +export function uvec3(...xs: any[]): Lit<"uvec3"> { + return $gvec3("uvec3", $uvec, xs); +} + +export function uvec4(): Lit<"uvec4">; +export function uvec4(x: NumericU): Lit<"uvec4">; +export function uvec4(x: Vec3Term, y: NumericU): Lit<"uvec4">; +export function uvec4(x: Vec2Term, y: Vec2Term): Lit<"uvec4">; +// prettier-ignore +export function uvec4(x: Vec2Term, y: NumericU, z: NumericU): Lit<"uvec4">; +// prettier-ignore +export function uvec4(x: NumericU, y: NumericU, z: NumericU, w: NumericU): Lit<"uvec4">; +export function uvec4(...xs: any[]): Lit<"uvec4"> { + return $gvec4("uvec4", $uvec, xs); +} + +export function bvec2(): Lit<"bvec2">; +export function bvec2(x: NumericB): Lit<"bvec2">; +// prettier-ignore +export function bvec2(x: NumericB, y: NumericB): Lit<"bvec2">; +// prettier-ignore +export function bvec2(...xs: any[]): Lit<"bvec2"> { + return $gvec2("bvec2", $bvec, xs); +} + +export function bvec3(): Lit<"bvec3">; +export function bvec3(x: NumericB): Lit<"bvec3">; +export function bvec3(x: Vec2Term, y: NumericB): Lit<"bvec3">; +// prettier-ignore +export function bvec3(x: NumericB, y: NumericB, z: NumericB): Lit<"bvec3">; +export function bvec3(...xs: any[]): Lit<"bvec3"> { + return $gvec3("bvec3", $bvec, xs); +} + +export function bvec4(): Lit<"bvec4">; +export function bvec4(x: NumericB): Lit<"bvec4">; +export function bvec4(x: Vec3Term, y: NumericB): Lit<"bvec4">; +export function bvec4(x: Vec2Term, y: Vec2Term): Lit<"bvec4">; +// prettier-ignore +export function bvec4(x: Vec2Term, y: NumericB, z: NumericB): Lit<"bvec4">; +// prettier-ignore +export function bvec4(x: NumericB, y: NumericB, z: NumericB, w: NumericB): Lit<"bvec4">; +export function bvec4(...xs: any[]): Lit<"bvec4"> { + return $gvec4("bvec4", $bvec, xs); +} + +export function mat2(): Lit<"mat2">; +export function mat2(x: NumericF): Lit<"mat2">; +export function mat2(x: Vec2Term, y: Vec2Term): Lit<"mat2">; +// prettier-ignore +export function mat2(a: NumericF, b: NumericF, c: NumericF, d: NumericF): Lit<"mat2">; +export function mat2(...xs: any[]): Lit<"mat2"> { + return $gmat("mat2", ["n", "n", "vv"], xs); +} + +export function mat3(): Lit<"mat3">; +export function mat3(x: NumericF): Lit<"mat3">; +// prettier-ignore +export function mat3(x: Vec3Term, y: Vec3Term, z: Vec3Term): Lit<"mat3">; +// prettier-ignore +export function mat3(a: NumericF, b: NumericF, c: NumericF, d: NumericF, e: NumericF, f: NumericF, g: NumericF, h: NumericF, i: NumericF): Lit<"mat3">; +export function mat3(...xs: any[]): Lit<"mat3"> { + return $gmat("mat3", ["n", "n", , "vvv"], xs); +} + +export function mat4(): Lit<"mat4">; +export function mat4(x: NumericF): Lit<"mat4">; +// prettier-ignore +export function mat4(x: Vec4Term, y: Vec4Term, z: Vec4Term, w: Vec4Term): Lit<"mat4">; +// prettier-ignore +export function mat4(a: NumericF, b: NumericF, c: NumericF, d: NumericF, e: NumericF, f: NumericF, g: NumericF, h: NumericF, i: NumericF, j: NumericF, k: NumericF, l: NumericF, m: NumericF, n: NumericF, o: NumericF, p: NumericF): Lit<"mat4">; +export function mat4(...xs: any[]): Lit<"mat4"> { + return $gmat("mat4", ["n", "n", , , "vvvv"], xs); +} diff --git a/packages/shader-ast/src/ast/ops.ts b/packages/shader-ast/src/ast/ops.ts new file mode 100644 index 0000000000..74a5bc5ff7 --- /dev/null +++ b/packages/shader-ast/src/ast/ops.ts @@ -0,0 +1,318 @@ +import { IObjectOf } from "@thi.ng/api"; +import { isNumber } from "@thi.ng/checks"; +import { + Op1, + Op2, + Sym, + Term +} from "../api/nodes"; +import { ComparisonOperator, Operator } from "../api/ops"; +import { + BoolTerm, + FloatTerm, + IntTerm, + Mat2Term, + Mat3Term, + Mat4Term, + UintTerm, + Vec2Term, + Vec3Term, + Vec4Term +} from "../api/terms"; +import { + Comparable, + Int, + IVec, + Mat, + Prim, + Type, + UVec, + Vec +} from "../api/types"; +import { isMat, isVec } from "./checks"; +import { numberWithMatchingType } from "./item"; +import { float } from "./lit"; + +export const op1 = ( + op: Operator, + val: Term, + post = false +): Op1 => ({ + tag: "op1", + type: val.type, + op, + val, + post +}); + +const OP_INFO: IObjectOf = { + mave: "mv", + vema: "vm", + vefl: "vn", + mafl: "vn", + flve: "nv", + flma: "nv", + ivin: "vn", + iniv: "nv", + uvui: "vn", + uiuv: "nv" +}; + +export const op2 = ( + op: Operator, + _l: Term | number, + _r: Term | number, + rtype?: Type, + info?: string +): Op2 => { + const nl = isNumber(_l); + const nr = isNumber(_r); + let type: Type; + let l: Term; + let r: Term; + if (nl) { + if (nr) { + // (number, number) + l = float(_l); + r = float(_r); + type = "float"; + } else { + // (number, term) + r = >_r; + l = numberWithMatchingType(r, _l); + type = r.type; + } + } else if (nr) { + // (term, number) + l = >_l; + r = numberWithMatchingType(l, _r); + type = l.type; + } else { + // (term, term) + l = >_l; + r = >_r; + type = + rtype || + (isVec(l) + ? l.type + : isVec(r) + ? r.type + : isMat(r) + ? r.type + : l.type); + } + return { + tag: "op2", + type: rtype || type!, + info: info || OP_INFO[l!.type.substr(0, 2) + r!.type.substr(0, 2)], + op, + l: l!, + r: r! + }; +}; + +export const inc = (t: Sym): Op1 => + op1("++", t, true); + +export const dec = (t: Sym): Op1 => + op1("--", t, true); + +// prettier-ignore +export function add(l: Term, r: Term): Op2; +export function add(l: number, r: Term): Op2; +export function add(l: Term, r: number): Op2; +// prettier-ignore +export function add(l: FloatTerm | number, r: Term): Op2; +// prettier-ignore +export function add(l: Term, r: FloatTerm | number): Op2; +// prettier-ignore +export function add(l: IntTerm | number, r: Term): Op2; +// prettier-ignore +export function add(l: Term, r: IntTerm | number): Op2; +// prettier-ignore +export function add(l: Term | number, r: Term | number): Op2 { + return op2("+", l, r); +} + +// prettier-ignore +export function sub(l: Term, r: Term): Op2; +export function sub(l: number, r: Term): Op2; +export function sub(l: Term, r: number): Op2; +// prettier-ignore +export function sub(l: FloatTerm | number, r: Term): Op2; +// prettier-ignore +export function sub(l: Term, r: FloatTerm | number): Op2; +// prettier-ignore +export function sub(l: IntTerm | number, r: Term): Op2; +// prettier-ignore +export function sub(l: Term, r: IntTerm| number): Op2; +export function sub(l: Term | number, r: Term | number): Op2 { + return op2("-", l, r); +} + +// prettier-ignore +export function mul(l: Term, r: Term): Op2; +export function mul(l: number, r: Term): Op2; +export function mul(l: Term, r: number): Op2; +// prettier-ignore +export function mul(l: FloatTerm | number, r: Term): Op2; +// prettier-ignore +export function mul(l: Term, r: FloatTerm | number): Op2; +// prettier-ignore +export function mul(l: IntTerm | number, r: Term): Op2; +// prettier-ignore +export function mul(l: Term, r: IntTerm | number): Op2; +export function mul(l: Mat2Term, r: Vec2Term): Op2<"vec2">; +export function mul(l: Mat3Term, r: Vec3Term): Op2<"vec3">; +export function mul(l: Mat4Term, r: Vec4Term): Op2<"vec4">; +export function mul(l: Vec2Term, r: Mat2Term): Op2<"vec2">; +export function mul(l: Vec3Term, r: Mat3Term): Op2<"vec3">; +export function mul(l: Vec4Term, r: Mat4Term): Op2<"vec4">; +export function mul(l: Term | number, r: Term | number): Op2 { + return op2( + "*", + l, + r, + !isNumber(l) && !isNumber(r) && isMat(l) && isVec(r) + ? r.type + : undefined + ); +} + +// prettier-ignore +export function div(l: Term, r: Term): Op2; +export function div(l: number, r: Term): Op2; +export function div(l: Term, r: number): Op2; +// prettier-ignore +export function div(l: FloatTerm | number, r: Term): Op2; +// prettier-ignore +export function div(l: Term, r: FloatTerm | number): Op2; +// prettier-ignore +export function div(l: IntTerm | number, r: Term): Op2; +// prettier-ignore +export function div(l: Term, r: IntTerm | number): Op2; +export function div(l: Term | number, r: Term | number): Op2 { + return op2("/", l, r); +} + +/** + * Integer % (modulo) operator + * + * @param l + * @param b + */ +// prettier-ignore +export function modi(l: Term, r: Term): Op2; +// prettier-ignore +export function modi(l: IntTerm | number, r: Term): Op2; +// prettier-ignore +export function modi(l: Term, r: IntTerm | number): Op2; +// prettier-ignore +export function modi(l: UintTerm | number, r: Term): Op2; +// prettier-ignore +export function modi(l: Term, r: UintTerm | number): Op2; +export function modi(l: Term | number, r: Term | number): Op2 { + return op2( + "%", + isNumber(l) ? numberWithMatchingType(>r, l) : l, + isNumber(r) ? numberWithMatchingType(>l, r) : r + ); +} + +export const neg = (val: Term) => + op1("-", val); + +/** + * Multiply-add: a * b + c. All must be of same type. + * + * @param a + * @param b + * @param c + */ +export const madd = < + A extends Prim | IVec | UVec | "int" | "uint", + B extends A, + C extends B +>( + a: Term, + b: Term, + c: Term +): Term => add(mul(>a, b), c); + +/** + * Add-multiply: (a + b) * c. All must be of same type. + * + * @param a + * @param b + * @param c + */ +export const addm = ( + a: Term, + b: Term, + c: Term +): Term => mul(add(>a, b), c); + +export const not = (val: BoolTerm) => op1("!", val); +export const or = (a: BoolTerm, b: BoolTerm) => op2("||", a, b); +export const and = (a: BoolTerm, b: BoolTerm) => op2("&&", a, b); + +const cmp = (op: ComparisonOperator) => ( + a: Term, + b: Term +): BoolTerm => op2(op, a, b, "bool"); + +export const eq = cmp("=="); +export const neq = cmp("!="); +export const lt = cmp("<"); +export const lte = cmp("<="); +export const gt = cmp(">"); +export const gte = cmp(">="); + +export const bitnot = | Term>( + val: T +) => op1("~", val); + +// prettier-ignore +export function bitand(l: Term, r: Term): Term; +// prettier-ignore +export function bitand(l: Term, r: IntTerm | number): Term; +// prettier-ignore +export function bitand(l: IntTerm | number, r: Term): Term; +// prettier-ignore +export function bitand(l: Term, r: UintTerm | number): Term; +// prettier-ignore +export function bitand(l: UintTerm | number, r: Term): Term; +// prettier-ignore +export function bitand(l: Term | number, r: Term | number): Op2 { + return op2("&", l, r, undefined); +} + +// prettier-ignore +export function bitor(l: Term, r: Term): Term; +// prettier-ignore +export function bitor(l: Term, r: IntTerm | number): Term; +// prettier-ignore +export function bitor(l: IntTerm | number, r: Term): Term; +// prettier-ignore +export function bitor(l: Term, r: UintTerm | number): Term; +// prettier-ignore +export function bitor(l: UintTerm | number, r: Term): Term; +// prettier-ignore +export function bitor(l: Term | number, r: Term | number): Op2 { + return op2("|", l, r, undefined); +} + +// prettier-ignore +export function bitxor(l: Term, r: Term): Term; +// prettier-ignore +export function bitxor(l: Term, r: IntTerm | number): Term; +// prettier-ignore +export function bitxor(l: IntTerm | number, r: Term): Term; +// prettier-ignore +export function bitxor(l: Term, r: UintTerm | number): Term; +// prettier-ignore +export function bitxor(l: UintTerm | number, r: Term): Term; +// prettier-ignore +export function bitxor(l: Term | number, r: Term | number): Op2 { + return op2("^", l, r, undefined); +} diff --git a/packages/shader-ast/src/ast/scope.ts b/packages/shader-ast/src/ast/scope.ts new file mode 100644 index 0000000000..adeda40c35 --- /dev/null +++ b/packages/shader-ast/src/ast/scope.ts @@ -0,0 +1,173 @@ +import { Fn, Fn2 } from "@thi.ng/api"; +import { isArray } from "@thi.ng/checks"; +import { DGraph } from "@thi.ng/dgraph"; +import { + Assign, + Branch, + Decl, + FnCall, + Func, + FuncReturn, + Lit, + Op1, + Op2, + Scope, + Sym, + Term, + Ternary +} from "../api/nodes"; +import { Type } from "../api/types"; +import { isMat, isVec } from "./checks"; + +/** + * Helper function for `walk()`. Returns child nodes for any control + * flow nodes containing a child scope. + * + * @see allChildren + */ +export const scopedChildren = (t: Term) => + t.tag === "fn" || t.tag === "for" || t.tag == "while" + ? (>t).scope.body + : t.tag === "if" + ? (t).f + ? (t).t.body.concat((t).f!.body) + : (t).t.body + : undefined; + +/** + * Helper function for `walk()`. Returns an array of all child nodes for + * a given term (if any). + * + * @see scopedChildren + */ +export const allChildren = (t: Term) => + scopedChildren(t) || + (t.tag === "scope" + ? (t).body + : t.tag === "ternary" + ? [(>t).t, (>t).f] + : t.tag === "ret" + ? [(>t).val] + : t.tag === "call" || t.tag === "call_i" + ? (>t).args + : t.tag === "sym" && (>t).init + ? [(>t).init] + : t.tag === "decl" + ? [(>t).id] + : t.tag === "op1" || t.tag === "swizzle" + ? [(>t).val] + : t.tag === "op2" + ? [(>t).l, (>t).r] + : t.tag === "assign" + ? [(>t).r] + : isVec(t) || isMat(t) + ? (>t).val + : undefined); + +/** + * Traverses given AST in depth-first order and applies `visit` and + * `children` fns to each node. Descends only further if `children` + * returns an array of child nodes. The `visit` function must accept 2 + * args: the accumulator (`acc`) given to `walk` and a tree node. The + * return value of `visit` becomes the new `acc` value, much like in a + * reduce operation. `walk` itself returns the final `acc`. + * + * If `pre` is true (default), the `visit` function will be called prior + * to visiting a node's children. If false, the visitor is called on the + * way back up. + * + * @param visit + * @param children + * @param acc + * @param tree + * @param pre + */ +export const walk = ( + visit: Fn2, T>, + children: Fn, Term[] | undefined>, + acc: T, + tree: Term | Term[], + pre = true +) => { + if (isArray(tree)) { + tree.forEach((x) => (acc = walk(visit, children, acc, x, pre))); + } else { + pre && (acc = visit(acc, tree)); + const c = children(tree); + c && (acc = walk(visit, children, acc, c, pre)); + !pre && (acc = visit(acc, tree)); + } + return acc; +}; + +/** + * Builds dependency graph of given function, by recursively adding all + * function dependencies. Returns graph. + * + * @param fn + * @param graph + */ +export const buildCallGraph = ( + fn: Func, + graph: DGraph> = new DGraph() +): DGraph> => + fn.deps && fn.deps.length + ? fn.deps.reduce( + (graph, d) => buildCallGraph(d, graph.addDependency(fn, d)), + graph + ) + : graph.addNode(fn); + +export const decl = (id: Sym): Decl => ({ + tag: "decl", + type: id.type, + id +}); + +/** + * Wraps the given AST node array in `scope` node, optionally as global + * scope (default false). The interpretation of the global flag is + * dependent on the target code gen. I.e. for GLSL / JS, the flag + * disables wrapping the scope's body in `{}`, but else has no + * difference. In general this node type only serves as internal + * mechanism for various control flow AST nodes and should not need to + * be used directly from user land code (though might be useful to + * create custom / higher level control flow nodes). + * + * @param body + * @param global + */ +export const scope = (body: (Term | null)[], global = false): Scope => ({ + tag: "scope", + type: "void", + body: []>( + body + .filter((x) => x != null) + .map((x) => (x!.tag === "sym" ? decl(>x) : x)) + ), + global +}); + +/** + * Takes an array of global sym/var definitions (`input()`, `output()`, + * `uniform()`) and functions defined via `defn()`. Constructs the call + * graph of all transitively used functions and bundles everything in + * topological order within a global scope object, which is then + * returned to the user and can be passed to a target codegen for full + * program output. + * + * @see scope + * @see input + * @see output + * @see uniform + * + * @param body + */ +export const program = (body: (Sym | Func)[]) => { + const syms = body.filter((x) => x.tag !== "fn"); + const g = body.reduce( + (acc, x) => (x.tag === "fn" ? buildCallGraph(>x, acc) : acc), + new DGraph>() + ); + return scope(syms.concat(g.sort()), true); +}; diff --git a/packages/shader-ast/src/ast/swizzle.ts b/packages/shader-ast/src/ast/swizzle.ts new file mode 100644 index 0000000000..8ad57669e6 --- /dev/null +++ b/packages/shader-ast/src/ast/swizzle.ts @@ -0,0 +1,126 @@ +import { Select4 } from "@thi.ng/api"; +import { Swizzle, Term } from "../api/nodes"; +import { + Swizzle2, + Swizzle2_1, + Swizzle2_2, + Swizzle2_3, + Swizzle3, + Swizzle3_1, + Swizzle3_2, + Swizzle3_3, + Swizzle4, + Swizzle4_1, + Swizzle4_2, + Swizzle4_3 +} from "../api/swizzles"; +import { + BVec2Term, + BVec3Term, + BVec4Term, + IVec2Term, + IVec3Term, + IVec4Term, + UVec2Term, + UVec3Term, + UVec4Term, + Vec2Term, + Vec3Term, + Vec4Term +} from "../api/terms"; +import { + BVec, + IVec, + Type, + UVec, + Vec +} from "../api/types"; + +// prettier-ignore +export function $(a: Vec2Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: Vec3Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: Vec4Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: IVec2Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: IVec3Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: IVec4Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: UVec2Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: UVec3Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: UVec4Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: BVec2Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: BVec3Term, id: T): Swizzle>; +// prettier-ignore +export function $(a: BVec4Term, id: T): Swizzle>; +export function $(val: Term, id: string): Swizzle { + const type = val.type[0]; + const rtype = (a: Type, b: string) => + id.length === 1 ? a : (b + id.length); + return { + tag: "swizzle", + type: + type === "i" + ? rtype("int", "ivec") + : type === "u" + ? rtype("uint", "uvec") + : type === "b" + ? rtype("bool", "bvec") + : rtype("float", "vec"), + val, + id + }; +} + +export const $x = ( + val: Term +): Swizzle> => + $(val, "x"); + +export const $y = ( + val: Term +): Swizzle> => + $(val, "y"); + +export const $z = < + T extends + | "vec3" + | "vec4" + | "ivec3" + | "ivec4" + | "uvec3" + | "uvec4" + | "bvec3" + | "bvec4" +>( + val: Term +): Swizzle> => + $(val, "z"); + +export const $w = ( + val: Term +): Swizzle> => + $(val, "w"); + +export function $xy(val: Term): Swizzle<"vec2">; +export function $xy(val: Term): Swizzle<"ivec2">; +export function $xy(val: Term): Swizzle<"uvec2">; +export function $xy(val: Term): Swizzle<"bvec2">; +export function $xy(val: any): Swizzle { + return $(val, "xy"); +} + +export function $xyz(val: Term<"vec3" | "vec4">): Swizzle<"vec3">; +export function $xyz(val: Term<"ivec3" | "ivec4">): Swizzle<"ivec3">; +export function $xyz(val: Term<"uvec3" | "uvec4">): Swizzle<"uvec3">; +export function $xyz(val: Term<"bvec3" | "bvec4">): Swizzle<"bvec3">; +export function $xyz(val: any): Swizzle { + return $(val, "xyz"); +} diff --git a/packages/shader-ast/src/ast/sym.ts b/packages/shader-ast/src/ast/sym.ts new file mode 100644 index 0000000000..d4d5c3392d --- /dev/null +++ b/packages/shader-ast/src/ast/sym.ts @@ -0,0 +1,114 @@ +import { assert } from "@thi.ng/api"; +import { isString } from "@thi.ng/checks"; +import { illegalArgs } from "@thi.ng/errors"; +import { Lit, Sym, Term } from "../api/nodes"; +import { SymOpts } from "../api/syms"; +import { ArrayTypeMap, Type } from "../api/types"; +import { gensym } from "./idgen"; + +export function sym(init: Term): Sym; +export function sym(type: T): Sym; +export function sym(type: T, opts: SymOpts): Sym; +export function sym(type: T, init: Term): Sym; +export function sym(type: T, id: string): Sym; +// prettier-ignore +export function sym(type: T, id: string, opts: SymOpts): Sym; +// prettier-ignore +export function sym(type: T, opts: SymOpts, init: Term): Sym; +// prettier-ignore +export function sym(type: T, id: string, opts: SymOpts, init: Term): Sym; +export function sym(type: any, ...xs: any[]): Sym { + let id: string; + let opts: SymOpts; + let init: Term; + switch (xs.length) { + case 0: + if (!isString(type)) { + init = type; + type = init.type; + } + break; + case 1: + if (isString(xs[0])) { + id = xs[0]; + } else if (xs[0].tag) { + init = xs[0]; + } else { + opts = xs[0]; + } + break; + case 2: + if (isString(xs[0])) { + [id, opts] = xs; + } else { + [opts, init] = xs; + } + break; + case 3: + [id, opts, init] = xs; + break; + default: + illegalArgs(); + } + return { + tag: "sym", + type, + id: id! || gensym(), + opts: opts! || {}, + init: init! + }; +} + +export const constSym = ( + type: T, + id?: string, + opts?: SymOpts, + init?: Term +) => sym(type, id || gensym(), { const: true, ...opts }, init!); + +/** + * Defines a new symbol with optional initial array values. + * + * Important: Array initializers are UNSUPPORTED in GLSL ES v1 (WebGL), + * any code using such initializers will only work under WebGL2 or other + * targets. + */ +export const arraySym = ( + type: T, + id?: string, + opts: SymOpts = {}, + init?: (Lit | Sym)[] +): Sym => { + if (init && opts.num == null) { + opts.num = init.length; + } + assert(opts.num != null, "missing array length"); + init && + assert( + opts.num === init.length, + `expected ${opts.num} items in array, but got ${init.length}` + ); + const atype = (type + "[]"); + return { + tag: "sym", + type: atype, + id: id || gensym(), + opts, + init: init + ? { + tag: "array_init", + type: atype, + init + } + : undefined + }; +}; + +export const input = (type: T, id: string, opts?: SymOpts) => + sym(type, id, { q: "in", type: "in", ...opts }); + +export const output = (type: T, id: string, opts?: SymOpts) => + sym(type, id, { q: "out", type: "out", ...opts }); + +export const uniform = (type: T, id: string, opts?: SymOpts) => + sym(type, id, { q: "in", type: "uni", ...opts }); diff --git a/packages/shader-ast/src/builtin/bvec.ts b/packages/shader-ast/src/builtin/bvec.ts new file mode 100644 index 0000000000..9181627c6f --- /dev/null +++ b/packages/shader-ast/src/builtin/bvec.ts @@ -0,0 +1,77 @@ +import { FnCall, Term } from "../api/nodes"; +import { BVec } from "../api/types"; +import { builtinCall } from "../ast/function"; + +const $bvec = (t: string) => ("bvec" + t[t.length - 1]); + +const $call = (fn: string, a: Term, b: Term) => + builtinCall(fn, $bvec(a.type), a, b); + +// prettier-ignore +export function lessThan(a: Term, b: Term): FnCall<"bvec2">; +// prettier-ignore +export function lessThan(a: Term, b: Term): FnCall<"bvec3">; +// prettier-ignore +export function lessThan(a: Term, b: Term): FnCall<"bvec4">; +export function lessThan(a: Term, b: Term) { + return $call("lessThan", a, b); +} + +// prettier-ignore +export function lessThanEqual(a: Term, b: Term): FnCall<"bvec2">; +// prettier-ignore +export function lessThanEqual(a: Term, b: Term): FnCall<"bvec3">; +// prettier-ignore +export function lessThanEqual(a: Term, b: Term): FnCall<"bvec4">; +export function lessThanEqual(a: Term, b: Term) { + return $call("lessThanEqual", a, b); +} + +// prettier-ignore +export function greaterThan(a: Term, b: Term): FnCall<"bvec2">; +// prettier-ignore +export function greaterThan(a: Term, b: Term): FnCall<"bvec3">; +// prettier-ignore +export function greaterThan(a: Term, b: Term): FnCall<"bvec4">; +export function greaterThan(a: Term, b: Term) { + return $call("greaterThan", a, b); +} + +// prettier-ignore +export function greaterThanEqual(a: Term, b: Term): FnCall<"bvec2">; +// prettier-ignore +export function greaterThanEqual(a: Term, b: Term): FnCall<"bvec3">; +// prettier-ignore +export function greaterThanEqual(a: Term, b: Term): FnCall<"bvec4">; +export function greaterThanEqual(a: Term, b: Term) { + return $call("greaterThanEqual", a, b); +} + +// prettier-ignore +export function equal(a: Term, b: Term): FnCall<"bvec2">; +// prettier-ignore +export function equal(a: Term, b: Term): FnCall<"bvec3">; +// prettier-ignore +export function equal(a: Term, b: Term): FnCall<"bvec4">; +export function equal(a: Term, b: Term) { + return $call("equal", a, b); +} + +// prettier-ignore +export function notEqual(a: Term, b: Term): FnCall<"bvec2">; +// prettier-ignore +export function notEqual(a: Term, b: Term): FnCall<"bvec3">; +// prettier-ignore +export function notEqual(a: Term, b: Term): FnCall<"bvec4">; +export function notEqual(a: Term, b: Term) { + return $call("notEqual", a, b); +} + +export const _any = (v: Term): FnCall<"bool"> => + builtinCall("any", "bool", v); + +export const all = (v: Term): FnCall<"bool"> => + builtinCall("all", "bool", v); + +export const _not = (v: Term) => + builtinCall("not", v.type, v); diff --git a/packages/shader-ast/src/builtin/math.ts b/packages/shader-ast/src/builtin/math.ts new file mode 100644 index 0000000000..48d4d6c956 --- /dev/null +++ b/packages/shader-ast/src/builtin/math.ts @@ -0,0 +1,193 @@ +import { FnCall, Sym, Term } from "../api/nodes"; +import { + BoolTerm, + BVec2Term, + BVec3Term, + BVec4Term, + FloatTerm, + IntTerm, + IVec2Term, + IVec3Term, + IVec4Term, + UintTerm, + UVec2Term, + UVec3Term, + UVec4Term, + Vec2Term, + Vec3Term, + Vec4Term +} from "../api/terms"; +import { Mat, Prim, Vec } from "../api/types"; +import { builtinCall } from "../ast/function"; +import { matchingBoolType, matchingPrimFor } from "../ast/item"; + +const primOp1 = (name: string) => (a: Term) => + builtinCall(name, a.type, a); + +const primOp2 = (name: string) => ( + a: Term, + b: Term +) => builtinCall(name, a.type, a, b); + +const primOp3 = (name: string) => ( + a: Term, + b: Term, + c: Term +) => builtinCall(name, a.type, a, b, c); + +/** + * Returns normalized version of given vector. + * + * @param v + */ +export const normalize = (v: Term) => + builtinCall("normalize", v.type, v); + +/** + * Returns length / magnitude of given vector. + * + * @param v + */ +export const length = (v: Term) => + builtinCall("length", "float", v); + +export const distance = (a: Term, b: Term) => + builtinCall("distance", "float", a, b); + +/** + * Returns dot product of given vectors. + * + * @param a + * @param b + */ +export const dot = (a: Term, b: Term) => + builtinCall("dot", "float", a, b); + +/** + * Returns cross product of given 3D vectors. + * + * @param a + * @param b + */ +export const cross = (a: Vec3Term, b: Vec3Term) => + builtinCall("cross", a.type, a, b); + +export const reflect = (i: Term, n: Term) => + builtinCall("reflect", i.type, i, n); + +export const refract = ( + i: Term, + n: Term, + ior: FloatTerm +) => builtinCall("refract", i.type, i, n, ior); + +export const faceForward = ( + i: Term, + n: Term, + nref: Term +) => builtinCall("faceForward", i.type, i, n, nref); + +export const min = primOp2("min"); +export const max = primOp2("max"); +export const clamp = primOp3("clamp"); + +export const step = primOp2("step"); +export const smoothstep = primOp3("smoothstep"); + +export const radians = primOp1("radians"); +export const degrees = primOp1("degrees"); + +export const cos = primOp1("cos"); +export const sin = primOp1("sin"); +export const tan = primOp1("tan"); +export const acos = primOp1("acos"); +export const asin = primOp1("asin"); + +export function atan(a: Term): FnCall; +// prettier-ignore +export function atan(a: Term, b: Term): FnCall; +export function atan(a: Term, b?: Term) { + const f = b + ? builtinCall("atan", a.type, a, b) + : builtinCall("atan", a.type, a); + b && (f.info = "nn"); + return f; +} + +export const pow = primOp2("pow"); +export const exp = primOp1("exp"); +export const log = primOp1("log"); +export const exp2 = primOp1("exp2"); +export const log2 = primOp1("log2"); +export const sqrt = primOp1("sqrt"); +export const inversesqrt = primOp1("inversesqrt"); + +export const abs = primOp1("abs"); +export const sign = primOp1("sign"); +export const floor = primOp1("floor"); +export const ceil = primOp1("ceil"); +export const fract = primOp1("fract"); + +export const powf = (x: Term, y: FloatTerm) => + pow(x, matchingPrimFor(x, y)); + +// prettier-ignore +export function mod(a: Term, b: Term): FnCall; +export function mod(a: Term, b: FloatTerm): FnCall; +export function mod(a: Term, b: Term): FnCall { + const f = builtinCall("mod", a.type, a, b); + b.type === "float" && (f.info = "n"); + return f; +} + +/** + * Important: Currently unsupported by JS target. + * + * TODO add additional metadata for JS (`b` is an output var), issue #96 + * + * @param a + * @param b + */ +// prettier-ignore +export const modf = (a: Term, b: Sym): FnCall => + builtinCall("modf", a.type, a, b); + +// prettier-ignore +export function mix(a: Term, b: Term, c: Term): FnCall; +// prettier-ignore +export function mix(a: Term, b: Term, c: FloatTerm): FnCall; +export function mix(a: Term, b: Term, c: Term): FnCall { + const f = builtinCall("mix", a.type, a, b, c); + c.type === "float" && (f.info = "n"); + return f; +} + +// prettier-ignore +export const matrixCompMult = (a: Term, b: Term) => + builtinCall("matrixCompMult", a.type, a, b); + +/** + * Important: Not available in JS / GLSL ES 100 + * + * @param a + */ +export function isnan(a: FloatTerm | IntTerm | UintTerm): BoolTerm; +export function isnan(a: Vec2Term | IVec2Term | UVec2Term): BVec2Term; +export function isnan(a: Vec3Term | IVec3Term | UVec3Term): BVec3Term; +export function isnan(a: Vec4Term | IVec4Term | UVec4Term): BVec4Term; +export function isnan(a: any): FnCall { + return builtinCall("isnan", matchingBoolType(a), a); +} + +/** + * Important: Not available in JS / GLSL ES 100 + * + * @param a + */ +export function isinf(a: FloatTerm | IntTerm | UintTerm): BoolTerm; +export function isinf(a: Vec2Term | IVec2Term | UVec2Term): BVec2Term; +export function isinf(a: Vec3Term | IVec3Term | UVec3Term): BVec3Term; +export function isinf(a: Vec4Term | IVec4Term | UVec4Term): BVec4Term; +export function isinf(a: any): FnCall { + return builtinCall("isinf", matchingBoolType(a), a); +} diff --git a/packages/shader-ast/src/builtins.ts b/packages/shader-ast/src/builtin/texture.ts similarity index 59% rename from packages/shader-ast/src/builtins.ts rename to packages/shader-ast/src/builtin/texture.ts index d8dcb43f58..70d0dce8f4 100644 --- a/packages/shader-ast/src/builtins.ts +++ b/packages/shader-ast/src/builtin/texture.ts @@ -1,232 +1,32 @@ import { illegalArgs } from "@thi.ng/errors"; +import { FnCall, Sym, Term } from "../api/nodes"; import { - BVec, FloatTerm, - FnCall, IntTerm, ISampler2DTerm, ISampler3DTerm, ISamplerCubeTerm, - IVec, IVec2Term, IVec3Term, - Mat, - Prim, - Sampler, Sampler2DTerm, Sampler3DTerm, SamplerCubeTerm, - Sym, - Term, USampler2DTerm, USampler3DTerm, USamplerCubeTerm, - Vec, Vec2Term, Vec3Term, Vec4Term -} from "./api"; +} from "../api/terms"; import { - builtinCall, - FLOAT0, - INT0, - isVec, - matchingPrimFor -} from "./ast"; - -const primOp1 = (name: string) => (a: Term) => - builtinCall(name, a.type, a); - -const primOp2 = (name: string) => ( - a: Term, - b: Term -) => builtinCall(name, a.type, a, b); - -const primOp3 = (name: string) => ( - a: Term, - b: Term, - c: Term -) => builtinCall(name, a.type, a, b, c); - -const $bvec = (t: string) => ("bvec" + t[t.length - 1]); - -/** - * Returns normalized version of given vector. - * - * @param v - */ -export const normalize = (v: Term) => - builtinCall("normalize", v.type, v); - -/** - * Returns length / magnitude of given vector. - * - * @param v - */ -export const length = (v: Term) => - builtinCall("length", "float", v); - -export const distance = (a: Term, b: Term) => - builtinCall("distance", "float", a, b); - -/** - * Returns dot product of given vectors. - * - * @param a - * @param b - */ -export const dot = (a: Term, b: Term) => - builtinCall("dot", "float", a, b); - -/** - * Returns cross product of given 3D vectors. - * - * @param a - * @param b - */ -export const cross = (a: Vec3Term, b: Vec3Term) => - builtinCall("cross", a.type, a, b); - -export const reflect = (i: Term, n: Term) => - builtinCall("reflect", i.type, i, n); - -export const refract = ( - i: Term, - n: Term, - ior: FloatTerm -) => builtinCall("refract", i.type, i, n, ior); - -export const faceForward = ( - i: Term, - n: Term, - nref: Term -) => builtinCall("faceForward", i.type, i, n, nref); - -export const min = primOp2("min"); -export const max = primOp2("max"); -export const clamp = primOp3("clamp"); - -export const step = primOp2("step"); -export const smoothstep = primOp3("smoothstep"); - -export const radians = primOp1("radians"); -export const degrees = primOp1("degrees"); - -export const cos = primOp1("cos"); -export const sin = primOp1("sin"); -export const tan = primOp1("tan"); -export const acos = primOp1("acos"); -export const asin = primOp1("asin"); -export const atan = primOp1("atan"); - -export const pow = primOp2("pow"); -export const exp = primOp1("exp"); -export const log = primOp1("log"); -export const exp2 = primOp1("exp2"); -export const log2 = primOp1("log2"); -export const sqrt = primOp1("sqrt"); -export const inversesqrt = primOp1("inversesqrt"); - -export const abs = primOp1("abs"); -export const sign = primOp1("sign"); -export const floor = primOp1("floor"); -export const ceil = primOp1("ceil"); -export const fract = primOp1("fract"); - -export const powf = (x: Term, y: FloatTerm) => - pow(x, matchingPrimFor(x, y)); - -// prettier-ignore -export function mod(a: Term, b: Term): FnCall; -export function mod(a: Term, b: FloatTerm): FnCall; -export function mod(a: Term, b: Term): FnCall { - const f = builtinCall("mod", a.type, a, b); - b.type === "float" && (f.info = "n"); - return f; -} - -// prettier-ignore -export function mix(a: Term, b: Term, c: Term): FnCall; -// prettier-ignore -export function mix(a: Term, b: Term, c: FloatTerm): FnCall; -export function mix(a: Term, b: Term, c: Term): FnCall { - const f = builtinCall("mix", a.type, a, b, c); - c.type === "float" && (f.info = "n"); - return f; -} - -// prettier-ignore -export const matrixCompMult = (a: Term, b: Term) => - builtinCall("matrixCompMult", a.type, a, b); - -// prettier-ignore -export function lessThan(a: Term, b: Term): FnCall<"bvec2">; -// prettier-ignore -export function lessThan(a: Term, b: Term): FnCall<"bvec3">; -// prettier-ignore -export function lessThan(a: Term, b: Term): FnCall<"bvec4">; -export function lessThan(a: Term, b: Term) { - return builtinCall("lessThan", $bvec(a.type), a, b); -} - -// prettier-ignore -export function lessThanEqual(a: Term, b: Term): FnCall<"bvec2">; -// prettier-ignore -export function lessThanEqual(a: Term, b: Term): FnCall<"bvec3">; -// prettier-ignore -export function lessThanEqual(a: Term, b: Term): FnCall<"bvec4">; -export function lessThanEqual(a: Term, b: Term) { - return builtinCall("lessThanEqual", $bvec(a.type), a, b); -} - -// prettier-ignore -export function greaterThan(a: Term, b: Term): FnCall<"bvec2">; -// prettier-ignore -export function greaterThan(a: Term, b: Term): FnCall<"bvec3">; -// prettier-ignore -export function greaterThan(a: Term, b: Term): FnCall<"bvec4">; -export function greaterThan(a: Term, b: Term) { - return builtinCall("greaterThan", $bvec(a.type), a, b); -} - -// prettier-ignore -export function greaterThanEqual(a: Term, b: Term): FnCall<"bvec2">; -// prettier-ignore -export function greaterThanEqual(a: Term, b: Term): FnCall<"bvec3">; -// prettier-ignore -export function greaterThanEqual(a: Term, b: Term): FnCall<"bvec4">; -export function greaterThanEqual(a: Term, b: Term) { - return builtinCall("greaterThanEqual", $bvec(a.type), a, b); -} - -// prettier-ignore -export function equal(a: Term, b: Term): FnCall<"bvec2">; -// prettier-ignore -export function equal(a: Term, b: Term): FnCall<"bvec3">; -// prettier-ignore -export function equal(a: Term, b: Term): FnCall<"bvec4">; -export function equal(a: Term, b: Term) { - return builtinCall("equal", $bvec(a.type), a, b); -} - -// prettier-ignore -export function notEqual(a: Term, b: Term): FnCall<"bvec2">; -// prettier-ignore -export function notEqual(a: Term, b: Term): FnCall<"bvec3">; -// prettier-ignore -export function notEqual(a: Term, b: Term): FnCall<"bvec4">; -export function notEqual(a: Term, b: Term) { - return builtinCall("notEqual", $bvec(a.type), a, b); -} - -export const _any = (v: Term): FnCall<"bool"> => - builtinCall("any", "bool", v); - -export const all = (v: Term): FnCall<"bool"> => - builtinCall("all", "bool", v); - -export const _not = (v: Term) => - builtinCall("not", v.type, v); + IVec, + Prim, + Sampler, + Vec +} from "../api/types"; +import { isVec } from "../ast/checks"; +import { builtinCall } from "../ast/function"; +import { FLOAT0, INT0 } from "../ast/lit"; const texRetType = (sampler: Term) => { const t = sampler.type[0]; @@ -246,6 +46,23 @@ const texRetType = (sampler: Term) => { : illegalArgs(`unknown sampler type ${sampler.type}`); }; +const $call = ( + name: string, + sampler: Term, + uv: Term, + bias?: FloatTerm +) => { + const f = builtinCall( + name, + texRetType(sampler), + sampler, + uv, + bias || FLOAT0 + ); + !isVec(f) && (f.info = "n"); + return f; +}; + // prettier-ignore export function textureSize(sampler: Sampler2DTerm, lod: IntTerm): FnCall<"ivec2">; // prettier-ignore @@ -290,15 +107,7 @@ export function texture(sampler: Term<"sampler2DShadow">, uvw: Vec3Term, bias?: export function texture(sampler: Term<"samplerCubeShadow">, uvw: Vec4Term, bias?: FloatTerm): FnCall<"float">; // prettier-ignore export function texture(sampler: Term, uv: Term, bias?: FloatTerm): FnCall { - const f = builtinCall( - "texture", - texRetType(sampler), - sampler, - uv, - bias || FLOAT0 - ); - !isVec(f) && (f.info = "n"); - return f; + return $call("texture", sampler, uv, bias); } // prettier-ignore @@ -317,15 +126,7 @@ export function textureProj(sampler: USampler3DTerm, uvw: Vec4Term, bias?: Float export function textureProj(sampler: Term<"sampler2DShadow">, uvw: Vec4Term, bias?: FloatTerm): FnCall<"float">; // prettier-ignore export function textureProj(sampler: Term, uv: Term, bias?: FloatTerm): FnCall { - const f = builtinCall( - "textureProj", - texRetType(sampler), - sampler, - uv, - bias || FLOAT0 - ); - !isVec(f) && (f.info = "n"); - return f; + return $call("textureProj", sampler, uv, bias); } // prettier-ignore @@ -350,15 +151,7 @@ export function textureLod(sampler: USamplerCubeTerm, uvw: Vec3Term, bias?: Floa export function textureLod(sampler: Term<"sampler2DShadow">, uvw: Vec3Term, bias?: FloatTerm): FnCall<"float">; // prettier-ignore export function textureLod(sampler: Term, uv: Term, bias?: FloatTerm): FnCall { - const f = builtinCall( - "textureLod", - texRetType(sampler), - sampler, - uv, - bias || FLOAT0 - ); - !isVec(f) && (f.info = "n"); - return f; + return $call("textureLod", sampler, uv, bias); } // prettier-ignore diff --git a/packages/shader-ast/src/index.ts b/packages/shader-ast/src/index.ts index fd3cf2858b..ae8963e02f 100644 --- a/packages/shader-ast/src/index.ts +++ b/packages/shader-ast/src/index.ts @@ -1,5 +1,29 @@ -export * from "./api"; -export * from "./ast"; -export * from "./builtins"; +export * from "./api/function"; +export * from "./api/nodes"; +export * from "./api/ops"; +export * from "./api/precision"; +export * from "./api/swizzles"; +export * from "./api/syms"; +export * from "./api/tags"; +export * from "./api/target"; +export * from "./api/terms"; +export * from "./api/types"; + +export * from "./ast/assign"; +export * from "./ast/checks"; +export * from "./ast/controlflow"; +export * from "./ast/function"; +export * from "./ast/indexed"; +export * from "./ast/item"; +export * from "./ast/lit"; +export * from "./ast/ops"; +export * from "./ast/scope"; +export * from "./ast/swizzle"; +export * from "./ast/sym"; + +export * from "./builtin/bvec"; +export * from "./builtin/math"; +export * from "./builtin/texture"; + export * from "./optimize"; export * from "./target"; diff --git a/packages/shader-ast/src/optimize.ts b/packages/shader-ast/src/optimize.ts index 4a41c77d93..74a798eda3 100644 --- a/packages/shader-ast/src/optimize.ts +++ b/packages/shader-ast/src/optimize.ts @@ -1,15 +1,15 @@ +import { NO_OP } from "@thi.ng/api"; +import { DEFAULT, defmulti } from "@thi.ng/defmulti"; import { Lit, Op1, Op2, Term -} from "./api"; -import { - allChildren, - isLitNumeric, - lit, - walk -} from "./ast"; +} from "./api/nodes"; +import { Operator } from "./api/ops"; +import { isLitNumeric } from "./ast/checks"; +import { lit } from "./ast/lit"; +import { allChildren, walk } from "./ast/scope"; const replaceNode = (node: any, next: any) => { for (let k in node) { @@ -18,6 +18,44 @@ const replaceNode = (node: any, next: any) => { return Object.assign(node, next); }; +const maybeFoldMath = (op: Operator, l: any, r: any) => + op === "+" + ? l + r + : op === "-" + ? l - r + : op === "*" + ? l * r + : op === "/" + ? l / r + : undefined; + +export const foldNode = defmulti, void>((t) => t.tag); +foldNode.add(DEFAULT, NO_OP); + +foldNode.addAll({ + op1: (t) => { + const op = >t; + if (op.op == "-" && isLitNumeric(op.val)) { + replaceNode(t, >op.val); + (op).val = -(op).val; + } + }, + + op2: (node) => { + const op = >node; + if (isLitNumeric(op.l) && isLitNumeric(op.r)) { + const vl = (>op.l).val; + const vr = (>op.r).val; + let res = maybeFoldMath(op.op, vl, vr); + if (res !== undefined) { + op.type === "int" && (res |= 0); + op.type === "uint" && (res >>>= 0); + replaceNode(node, lit(op.type, res)); + } + } + } +}); + /** * Traverses given AST and applies constant folding optimizations where * possible. Returns possibly updated tree (mutates original). @@ -50,47 +88,6 @@ const replaceNode = (node: any, next: any) => { * @param tree */ export const constantFolding = (tree: Term) => { - walk( - (_, node) => { - switch (node.tag) { - case "op1": { - const n = >node; - if (n.op == "-" && isLitNumeric(n.val)) { - replaceNode(node, >n.val); - (n).val = -(n).val; - } - break; - } - case "op2": { - const n = >node; - if (isLitNumeric(n.l) && isLitNumeric(n.r)) { - const vl = (>n.l).val; - const vr = (>n.r).val; - let res = - n.op === "+" - ? vl + vr - : n.op === "-" - ? vl - vr - : n.op === "*" - ? vl * vr - : n.op === "/" - ? vl / vr - : undefined; - if (res !== undefined) { - n.type === "int" && (res |= 0); - n.type === "uint" && (res >>>= 0); - replaceNode(node, lit(n.type, res)); - } - } - break; - } - default: - } - }, - allChildren, - null, - tree, - false - ); + walk((_, node) => foldNode(node), allChildren, null, tree, false); return tree; }; diff --git a/packages/shader-ast/src/target.ts b/packages/shader-ast/src/target.ts index 018f629ae1..040a350d44 100644 --- a/packages/shader-ast/src/target.ts +++ b/packages/shader-ast/src/target.ts @@ -1,7 +1,8 @@ import { Fn } from "@thi.ng/api"; import { DEFAULT, defmulti } from "@thi.ng/defmulti"; import { unsupported } from "@thi.ng/errors"; -import { TargetImpl, Term } from "./api"; +import { Term } from "./api/nodes"; +import { TargetImpl } from "./api/target"; /** * Takes an object of code generator functions and returns a new code diff --git a/packages/simd/.gitignore b/packages/simd/.gitignore new file mode 100644 index 0000000000..103ff13111 --- /dev/null +++ b/packages/simd/.gitignore @@ -0,0 +1 @@ +src/binary.ts diff --git a/packages/simd/.npmignore b/packages/simd/.npmignore new file mode 100644 index 0000000000..2f617619f1 --- /dev/null +++ b/packages/simd/.npmignore @@ -0,0 +1,19 @@ +.cache +.meta +.nyc_output +*.gz +*.html +*.svg +*.tgz +*.h +*.o +*.wat +!*.wasm +build +coverage +dev +doc +export +src* +test +tsconfig.json diff --git a/packages/simd/CHANGELOG.md b/packages/simd/CHANGELOG.md new file mode 100644 index 0000000000..fb7ad547ef --- /dev/null +++ b/packages/simd/CHANGELOG.md @@ -0,0 +1,25 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# 0.1.0 (2019-11-09) + + +### Bug Fixes + +* **simd:** add missing wasm exports ([f998f88](https://github.com/thi-ng/umbrella/commit/f998f883a10e1a663da7213fed49948c005fcdf1)) + + +### Features + +* **simd:** add clampn4_f32, sum4_f32 ([0e0dfde](https://github.com/thi-ng/umbrella/commit/0e0dfde150856ea62c0b316a3a6391dccd3646a8)) +* **simd:** add hadd* inline fns, update dot, normalize, mulv, sum ([a1011ea](https://github.com/thi-ng/umbrella/commit/a1011ead5ee1d55adbea1da1efcea2829b037210)) +* **simd:** add mag2/4, magsq2/4, move/extract inline fns, update tests, readme ([00ce05b](https://github.com/thi-ng/umbrella/commit/00ce05b5ec54e4ba1542e671de8dcd61b396a783)) +* **simd:** add matrix-vec mult fns, no async init, inline binary as b64 ([761dd98](https://github.com/thi-ng/umbrella/commit/761dd9822c4f78d3581a533385763cdc09154da9)) +* **simd:** add mix4_f32, mixn4_f32, mul_m22v2_aos, update test & readme ([d09f09e](https://github.com/thi-ng/umbrella/commit/d09f09ecd519c41db72e68a06d566190e57f647c)) +* **simd:** add more vector fns ([4f4cea4](https://github.com/thi-ng/umbrella/commit/4f4cea4ed912236aeacb19e0d50f171bf9dde15b)) +* **simd:** add new dot fns, tests, rename ([50bc9fc](https://github.com/thi-ng/umbrella/commit/50bc9fc85b141c11cedf66f4384561259f93fff9)) +* **simd:** add new fns, switch to f32x4 namespaced ops, update readme ([4023a8f](https://github.com/thi-ng/umbrella/commit/4023a8f02b9759bb0d3b11036de578e37b82493e)) +* **simd:** add new package ([eedb895](https://github.com/thi-ng/umbrella/commit/eedb89530555332103e3a32147c318592edf830b)) +* **simd:** add swizzle fns (disabled) ([a47ec4d](https://github.com/thi-ng/umbrella/commit/a47ec4dbc16271103a7b4aaca730677136275e9d)) diff --git a/packages/simd/LICENSE b/packages/simd/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/simd/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/simd/README.md b/packages/simd/README.md new file mode 100644 index 0000000000..59c0701189 --- /dev/null +++ b/packages/simd/README.md @@ -0,0 +1,176 @@ +# @thi.ng/simd + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/simd.svg)](https://www.npmjs.com/package/@thi.ng/simd) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/simd.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + +- [About](#about) +- [Available functions](#available-functions) +- [Status](#status) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) +- [Authors](#authors) +- [License](#license) + + + +## About + +[WebAssembly SIMD](https://github.com/WebAssembly/simd) vector +operations for array/batch processing, written in +[AssemblyScript](https://docs.assemblyscript.org/). These functions use +the CPU's vector instructions to process 128bit words at once, which is +the equivalent width of a 4D vector with 4x 32bit components. Several of +the provided functions can also be used to process 2D vectors. + +## Available functions + +See +[/assembly](https://github.com/thi-ng/umbrella/tree/master/packages/simd/assembly) +for sources: + +- `abs4_f32` +- `add4_f32` +- `addn4_f32` +- `clamp4_f32` +- `clampn4_f32` +- `div4_f32` (!) +- `divn4_f32` (!) +- `dot2_f32_aos` (2) +- `dot4_f32_aos` +- `dot4_f32_soa` +- `invsqrt4_f32` (!) +- `madd4_f32` +- `maddn4_f32` +- `mag2_f32_aos` +- `mag4_f32_aos` +- `magsq2_f32_aos` +- `magsq4_f32_aos` +- `max4_f32` +- `min4_f32` +- `mix4_f32` +- `mixn4_f32` +- `msub4_f32` +- `msubn4_f32` +- `mul4_f32` +- `muln4_f32` +- `mul_m22v2_aos` (2) +- `mul_m23v2_aos` (2) +- `mul_m44v4_aos` +- `neg4_f32` +- `normalize2_f32_aos` (2) +- `normalize4_f32_aos` +- `sqrt4_f32` (!) +- `sub4_f32` +- `subn4_f32` +- `sum4_f32` +- `swizzle4_32` (!) + +(!) Missing native implementation, waiting on... + +(2) 2x vec2 per iteration + +Also see +[src/api.ts](https://github.com/thi-ng/umbrella/tree/master/packages/simd/src/api.ts) +for documentation about the exposed TS/JS API... + +## Status + +ALPHA - experimental + +The [WebAssembly SIMD spec](https://github.com/WebAssembly/simd) is +still WIP and (at the time of writing) only partially implemented and +hidden behind feature flags. + +- NodeJS (v12.10+): `node --experimental-wasm-simd` +- Chrome: Enable SIMD support via [chrome://flags](chrome://flags) + +## Installation + +```bash +yarn add @thi.ng/simd +``` + +## Dependencies + +- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/master/packages/checks) + +## Usage examples + +```ts +import { init } from "@thi.ng/simd"; + +// the WASM module doesn't specify any own memory and it must be provided by user +// the returned object contains all available vector functions & memory views +// (an error will be thrown if WASM isn't available or SIMD unsupported) +const simd = init(new WebAssembly.Memory({ initial: 1 })); + +// input data: 3x vec4 buffers +const a = simd.f32.subarray(0, 4); +const b = simd.f32.subarray(4, 16); +const out = simd.f32.subarray(16, 18); + +a.set([1, 2, 3, 4]) +b.set([10, 20, 30, 40, 40, 30, 20, 10]); + +// compute dot products: dot(A[i], B[i]) +// by using 0 as stride for A, all dot products are using the same vec +simd.dot4_f32_aos( + out.byteOffset, // output addr / pointer + a.byteOffset, // vector A addr + b.byteOffset, // vector B addr + 2, // number of vectors to process + 1, // output stride (floats) + 0, // A stride (floats) + 4 // B stride (floats) +); + +// results for [dot(a0, b0), dot(a0, b1)] +out +// [300, 200] + +// mat4 * vec4 matrix-vector multiplies +const mat = simd.f32.subarray(0, 16); +const points = simd.f32.subarray(16, 24); + +// mat4 (col major) +mat.set([ + 10, 0, 0, 0, + 0, 20, 0, 0, + 0, 0, 30, 0, + 100, 200, 300, 1 +]); + +// vec4 array +points.set([ + 1, 2, 3, 1, + 4, 5, 6, 1, +]); + +simd.mul_m44v4_aos( + points.byteOffset, // output addr / pointer + mat.byteOffset, // mat4 addr + points.byteOffset, // vec4 addr + 2, // number of vectors to process + 4, // output stride (float) + 4 // vec stride (float) +); + +// transformed points +points +// [110, 240, 390, 1, 140, 300, 480, 1] +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2019 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/simd/assembly/abs.ts b/packages/simd/assembly/abs.ts new file mode 100644 index 0000000000..9780b8dbfc --- /dev/null +++ b/packages/simd/assembly/abs.ts @@ -0,0 +1,17 @@ +export function abs4_f32( + out: usize, + a: usize, + num: usize, + so: usize, + sa: usize +): usize { + so <<= 2; + sa <<= 2; + const res = out; + for (; num-- > 0; ) { + v128.store(out, f32x4.abs(v128.load(a))); + out += so; + a += sa; + } + return res; +} diff --git a/packages/simd/assembly/add.ts b/packages/simd/assembly/add.ts new file mode 100644 index 0000000000..c40de7fff7 --- /dev/null +++ b/packages/simd/assembly/add.ts @@ -0,0 +1,21 @@ +export function add4_f32( + out: usize, + a: usize, + b: usize, + num: usize, + so: usize, + sa: usize, + sb: usize +): usize { + so <<= 2; + sa <<= 2; + sb <<= 2; + const res = out; + for (; num-- > 0; ) { + v128.store(out, f32x4.add(v128.load(a), v128.load(b))); + out += so; + a += sa; + b += sb; + } + return res; +} diff --git a/packages/simd/assembly/addn.ts b/packages/simd/assembly/addn.ts new file mode 100644 index 0000000000..2699de96c6 --- /dev/null +++ b/packages/simd/assembly/addn.ts @@ -0,0 +1,19 @@ +export function addn4_f32( + out: usize, + a: usize, + n: f32, + num: usize, + so: usize, + sa: usize +): usize { + so <<= 2; + sa <<= 2; + const res = out; + const vn = f32x4.splat(n); + for (; num-- > 0; ) { + v128.store(out, f32x4.add(v128.load(a), vn)); + out += so; + a += sa; + } + return res; +} diff --git a/packages/simd/assembly/align.ts b/packages/simd/assembly/align.ts new file mode 100644 index 0000000000..07284621dd --- /dev/null +++ b/packages/simd/assembly/align.ts @@ -0,0 +1,12 @@ +// @ts-ignore: decorator +@inline +export function align(x: usize, base: usize): usize { + base--; + return (x + base) & ~base; +} + +// @ts-ignore: decorator +@inline +export function isAligned(x: usize, base: usize): boolean { + return (x & (base - 1)) === 0; +} diff --git a/packages/simd/assembly/clamp.ts b/packages/simd/assembly/clamp.ts new file mode 100644 index 0000000000..51fa0e619f --- /dev/null +++ b/packages/simd/assembly/clamp.ts @@ -0,0 +1,71 @@ +/** + * Takes three vec4 buffers, clamps `a` componentwise to `min(max(a, b), + * c)` and stores results in `out`. Both AOS / SOA layouts are + * supported, as long as all buffers are using the same layout. + * + * All strides must by multiples of 4. All pointers must be aligned to + * multiples of 16. Returns `out` pointer. + * + * Set `sb` and `sc` to 0 for clamping all `a` vectors against same + * bounds. + * + * @param out + * @param a + * @param b + * @param c + * @param num number of vec4 + * @param so out element stride + * @param sa A element stride + * @param sb B element stride + * @param sc C element stride + */ +export function clamp4_f32( + out: usize, + a: usize, + b: usize, + c: usize, + num: usize, + so: usize, + sa: usize, + sb: usize, + sc: usize +): usize { + const res = out; + so <<= 2; + sa <<= 2; + sb <<= 2; + sc <<= 2; + for (; num-- > 0; ) { + v128.store( + out, + f32x4.min(f32x4.max(v128.load(a), v128.load(b)), v128.load(c)) + ); + out += so; + a += sa; + b += sb; + c += sc; + } + return res; +} + +export function clampn4_f32( + out: usize, + a: usize, + b: f32, + c: f32, + num: usize, + so: usize, + sa: usize +): usize { + const res = out; + so <<= 2; + sa <<= 2; + const vmin = f32x4.splat(b); + const vmax = f32x4.splat(c); + for (; num-- > 0; ) { + v128.store(out, f32x4.min(f32x4.max(v128.load(a), vmin), vmax)); + out += so; + a += sa; + } + return res; +} diff --git a/packages/simd/assembly/div.ts b/packages/simd/assembly/div.ts new file mode 100644 index 0000000000..09d013ff55 --- /dev/null +++ b/packages/simd/assembly/div.ts @@ -0,0 +1,21 @@ +export function div4_f32( + out: usize, + a: usize, + b: usize, + num: usize, + so: usize, + sa: usize, + sb: usize +): usize { + so <<= 2; + sa <<= 2; + sb <<= 2; + const res = out; + for (; num-- > 0; ) { + v128.store(out, f32x4.div(v128.load(a), v128.load(b))); + out += so; + a += sa; + b += sb; + } + return res; +} diff --git a/packages/simd/assembly/divn.ts b/packages/simd/assembly/divn.ts new file mode 100644 index 0000000000..dd4dde38a3 --- /dev/null +++ b/packages/simd/assembly/divn.ts @@ -0,0 +1,19 @@ +export function divn4_f32( + out: usize, + a: usize, + n: f32, + num: usize, + so: usize, + sa: usize +): usize { + so <<= 2; + sa <<= 2; + const res = out; + const vn = f32x4.splat(n); + for (; num-- > 0; ) { + v128.store(out, f32x4.div(v128.load(a), vn)); + out += so; + a += sa; + } + return res; +} diff --git a/packages/simd/assembly/dot.ts b/packages/simd/assembly/dot.ts new file mode 100644 index 0000000000..367d3757bc --- /dev/null +++ b/packages/simd/assembly/dot.ts @@ -0,0 +1,108 @@ +import { __hadd2_f32, __hadd4_f32 } from "./inline/hadd"; + +/** + * Takes two densely packed vec2 AOS buffers `a` and `b`, computes their + * 2D dot products and stores results in `out`. Computes two results per + * iteration, hence `num` must be an even number or else the last vector + * will not be processed. `so` should be 1 for packed result buffer. + * + * `a` & `b` should be aligned to 16, `out` to multiples of 4. + * + * @param out + * @param a + * @param b + * @param num + * @param so + */ +export function dot2_f32_aos( + out: usize, + a: usize, + b: usize, + num: usize +): usize { + const res = out; + num >>= 1; + for (; num-- > 0; ) { + const m = __hadd2_f32(f32x4.mul(v128.load(a), v128.load(b))); + f32.store(out, f32x4.extract_lane(m, 0)); + f32.store(out, f32x4.extract_lane(m, 2), 4); + out += 8; + a += 16; + b += 16; + } + return res; +} + +/** + * Takes two vec4 AOS buffers, computes their dot products and stores + * results in `out`. `so` should be 1 for a packed result buffer. `sa` + * and `sb` indicate the stride lengths (in floats) between each vector + * in each respective buffer and should be a multiple of 4. + * + * @param out + * @param a + * @param b + * @param num + * @param so + * @param sa + * @param sb + */ +export function dot4_f32_aos( + out: usize, + a: usize, + b: usize, + num: usize, + so: usize, + sa: usize, + sb: usize +): usize { + const res = out; + so <<= 2; + sa <<= 2; + sb <<= 2; + // a1*b1 + a2*b2 + a3*b3 + a4*b4 + for (; num-- > 0; ) { + f32.store(out, __hadd4_f32(f32x4.mul(v128.load(a), v128.load(b)))); + out += so; + a += sa; + b += sb; + } + return res; +} + +export function dot4_f32_soa( + out: usize, + a: usize, + b: usize, + num: usize, + sa: usize, + sb: usize +): usize { + sa <<= 2; + sb <<= 2; + num >>= 2; + const sa2 = sa * 2; + const sb2 = sb * 2; + const sa3 = sa * 3; + const sb3 = sb * 3; + const res = out; + for (; num-- > 0; ) { + v128.store( + out, + f32x4.add( + f32x4.add( + f32x4.add( + f32x4.mul(v128.load(a), v128.load(b)), + f32x4.mul(v128.load(a + sa), v128.load(b + sb)) + ), + f32x4.mul(v128.load(a + sa2), v128.load(b + sb2)) + ), + f32x4.mul(v128.load(a + sa3), v128.load(b + sb3)) + ) + ); + out += 16; + sa += 16; + sb += 16; + } + return res; +} diff --git a/packages/simd/assembly/index.ts b/packages/simd/assembly/index.ts new file mode 100644 index 0000000000..6580a76ff6 --- /dev/null +++ b/packages/simd/assembly/index.ts @@ -0,0 +1,33 @@ +export * from "./abs"; +export * from "./add"; +export * from "./addn"; +export * from "./clamp"; + +// TODO waiting for native impl +// export * from "./div"; +// export * from "./divn"; + +export * from "./dot"; +export * from "./madd"; +export * from "./maddn"; +export * from "./mag"; +export * from "./magsq"; +export * from "./max"; +export * from "./min"; +export * from "./mix"; +export * from "./mixn"; +export * from "./msub"; +export * from "./msubn"; +export * from "./mul"; +export * from "./muln"; +export * from "./mulv"; +export * from "./neg"; +export * from "./normalize"; + +// TODO waiting for native impl +// export * from "./sqrt"; + +export * from "./sub"; +export * from "./subn"; +export * from "./sum"; +// export * from "./swizzle"; diff --git a/packages/simd/assembly/inline/hadd.ts b/packages/simd/assembly/inline/hadd.ts new file mode 100644 index 0000000000..aaa040f85e --- /dev/null +++ b/packages/simd/assembly/inline/hadd.ts @@ -0,0 +1,29 @@ +/** + * Pairwise horizontal sum of `v`: + * + * ``` + * [a, b, c, d] => [a+b, a+b, c+d, c+d] + * ``` + * + * @param v + */ +// @ts-ignore: decorator +@inline +export function __hadd2_f32(v: v128): v128 { + return f32x4.add(v, v128.shuffle(v, v, 1, 0, 3, 2)); +} + +/** + * Full horizontal sum of `v`: + * + * ``` + * [a, b, c, d] => a + c + b + d + * ``` + * @param v + */ +// @ts-ignore: decorator +@inline +export function __hadd4_f32(v: v128): f32 { + v = f32x4.add(v, v128.shuffle(v, v, 2, 3, 0, 1)); + return f32x4.extract_lane(v, 0) + f32x4.extract_lane(v, 1); +} diff --git a/packages/simd/assembly/inline/magsq.ts b/packages/simd/assembly/inline/magsq.ts new file mode 100644 index 0000000000..aa7266b1fc --- /dev/null +++ b/packages/simd/assembly/inline/magsq.ts @@ -0,0 +1,13 @@ +import { __hadd2_f32, __hadd4_f32 } from "./hadd"; + +// @ts-ignore: decorator +@inline +export function __magsq2(v: v128): v128 { + return __hadd2_f32(f32x4.mul(v, v)); +} + +// @ts-ignore: decorator +@inline +export function __magsq4(v: v128): f32 { + return __hadd4_f32(f32x4.mul(v, v)); +} diff --git a/packages/simd/assembly/madd.ts b/packages/simd/assembly/madd.ts new file mode 100644 index 0000000000..fd6d99664e --- /dev/null +++ b/packages/simd/assembly/madd.ts @@ -0,0 +1,46 @@ +/** + * Takes three vec4 buffers, computes componentwise a * b + c and stores + * results in `out`. Both AOS / SOA layouts are supported, as long as + * all buffers are using the same layout. + * + * All strides must by multiples of 4. All pointers must be aligned to + * multiples of 16. Returns `out` pointer. + * + * @param out + * @param a + * @param b + * @param c + * @param num number of vec4 + * @param so out element stride + * @param sa A element stride + * @param sb B element stride + * @param sc C element stride + */ +export function madd4_f32( + out: usize, + a: usize, + b: usize, + c: usize, + num: usize, + so: usize, + sa: usize, + sb: usize, + sc: usize +): usize { + const res = out; + so <<= 2; + sa <<= 2; + sb <<= 2; + sc <<= 2; + for (; num-- > 0; ) { + v128.store( + out, + f32x4.add(f32x4.mul(v128.load(a), v128.load(b)), v128.load(c)) + ); + out += so; + a += sa; + b += sb; + c += sc; + } + return res; +} diff --git a/packages/simd/assembly/maddn.ts b/packages/simd/assembly/maddn.ts new file mode 100644 index 0000000000..58dab925ee --- /dev/null +++ b/packages/simd/assembly/maddn.ts @@ -0,0 +1,23 @@ +export function maddn4_f32( + out: usize, + a: usize, + n: f32, + c: usize, + num: usize, + so: usize, + sa: usize, + sc: usize +): usize { + const res = out; + so <<= 2; + sa <<= 2; + sc <<= 2; + const vn = f32x4.splat(n); + for (; num-- > 0; ) { + v128.store(out, f32x4.add(f32x4.mul(v128.load(a), vn), v128.load(c))); + out += so; + a += sa; + c += sc; + } + return res; +} diff --git a/packages/simd/assembly/mag.ts b/packages/simd/assembly/mag.ts new file mode 100644 index 0000000000..2f15d45a79 --- /dev/null +++ b/packages/simd/assembly/mag.ts @@ -0,0 +1,32 @@ +import { __magsq2, __magsq4 } from "./inline/magsq"; + +export function mag2_f32_aos(out: usize, a: usize, num: usize): usize { + const res = out; + num >>= 1; + for (; num-- > 0; ) { + const v = __magsq2(v128.load(a)); + f32.store(out, sqrt(f32x4.extract_lane(v, 0))); + f32.store(out, sqrt(f32x4.extract_lane(v, 2)), 4); + out += 8; + a += 16; + } + return res; +} + +export function mag4_f32_aos( + out: usize, + a: usize, + num: usize, + so: usize, + sa: usize +): usize { + const res = out; + so <<= 2; + sa <<= 2; + for (; num-- > 0; ) { + f32.store(out, sqrt(__magsq4(v128.load(a)))); + out += so; + a += sa; + } + return res; +} diff --git a/packages/simd/assembly/magsq.ts b/packages/simd/assembly/magsq.ts new file mode 100644 index 0000000000..e260373d97 --- /dev/null +++ b/packages/simd/assembly/magsq.ts @@ -0,0 +1,32 @@ +import { __magsq2, __magsq4 } from "./inline/magsq"; + +export function magsq2_f32_aos(out: usize, a: usize, num: usize): usize { + const res = out; + num >>= 1; + for (; num-- > 0; ) { + const v = __magsq2(v128.load(a)); + f32.store(out, f32x4.extract_lane(v, 0)); + f32.store(out, f32x4.extract_lane(v, 2), 4); + out += 8; + a += 16; + } + return res; +} + +export function magsq4_f32_aos( + out: usize, + a: usize, + num: usize, + so: usize, + sa: usize +): usize { + const res = out; + so <<= 2; + sa <<= 2; + for (; num-- > 0; ) { + f32.store(out, __magsq4(v128.load(a))); + out += so; + a += sa; + } + return res; +} diff --git a/packages/simd/assembly/max.ts b/packages/simd/assembly/max.ts new file mode 100644 index 0000000000..cba8083fe8 --- /dev/null +++ b/packages/simd/assembly/max.ts @@ -0,0 +1,21 @@ +export function max4_f32( + out: usize, + a: usize, + b: usize, + num: usize, + so: usize, + sa: usize, + sb: usize +): usize { + so <<= 2; + sa <<= 2; + sb <<= 2; + const res = out; + for (; num-- > 0; ) { + v128.store(out, f32x4.max(v128.load(a), v128.load(b))); + out += so; + a += sa; + b += sb; + } + return res; +} diff --git a/packages/simd/assembly/min.ts b/packages/simd/assembly/min.ts new file mode 100644 index 0000000000..ec34b21a9a --- /dev/null +++ b/packages/simd/assembly/min.ts @@ -0,0 +1,21 @@ +export function min4_f32( + out: usize, + a: usize, + b: usize, + num: usize, + so: usize, + sa: usize, + sb: usize +): usize { + so <<= 2; + sa <<= 2; + sb <<= 2; + const res = out; + for (; num-- > 0; ) { + v128.store(out, f32x4.min(v128.load(a), v128.load(b))); + out += so; + a += sa; + b += sb; + } + return res; +} diff --git a/packages/simd/assembly/mix.ts b/packages/simd/assembly/mix.ts new file mode 100644 index 0000000000..115ce69feb --- /dev/null +++ b/packages/simd/assembly/mix.ts @@ -0,0 +1,29 @@ +export function mix4_f32( + out: usize, + a: usize, + b: usize, + t: usize, + num: usize, + so: usize, + sa: usize, + sb: usize, + st: usize +): usize { + const res = out; + so <<= 2; + sa <<= 2; + sb <<= 2; + st <<= 2; + for (; num-- > 0; ) { + const va = v128.load(a); + v128.store( + out, + f32x4.add(va, f32x4.mul(f32x4.sub(v128.load(b), va), v128.load(t))) + ); + out += so; + a += sa; + b += sb; + t += st; + } + return res; +} diff --git a/packages/simd/assembly/mixn.ts b/packages/simd/assembly/mixn.ts new file mode 100644 index 0000000000..275d5081ad --- /dev/null +++ b/packages/simd/assembly/mixn.ts @@ -0,0 +1,27 @@ +export function mixn4_f32( + out: usize, + a: usize, + b: usize, + t: f32, + num: usize, + so: usize, + sa: usize, + sb: usize +): usize { + const res = out; + so <<= 2; + sa <<= 2; + sb <<= 2; + const vt = f32x4.splat(t); + for (; num-- > 0; ) { + const va = v128.load(a); + v128.store( + out, + f32x4.add(va, f32x4.mul(f32x4.sub(v128.load(b), va), vt)) + ); + out += so; + a += sa; + b += sb; + } + return res; +} diff --git a/packages/simd/assembly/msub.ts b/packages/simd/assembly/msub.ts new file mode 100644 index 0000000000..c5f8039d7d --- /dev/null +++ b/packages/simd/assembly/msub.ts @@ -0,0 +1,46 @@ +/** + * Takes three vec4 buffers, computes componentwise a * b - c and stores + * results in `out`. Both AOS / SOA layouts are supported, as long as + * all buffers are using the same layout. + * + * All strides must by multiples of 4. All pointers must be aligned to + * multiples of 16. Returns `out` pointer. + * + * @param out + * @param a + * @param b + * @param c + * @param num number of vec4 + * @param so out element stride + * @param sa A element stride + * @param sb B element stride + * @param sc C element stride + */ +export function msub4_f32( + out: usize, + a: usize, + b: usize, + c: usize, + num: usize, + so: usize, + sa: usize, + sb: usize, + sc: usize +): usize { + const res = out; + so <<= 2; + sa <<= 2; + sb <<= 2; + sc <<= 2; + for (; num-- > 0; ) { + v128.store( + out, + f32x4.sub(f32x4.mul(v128.load(a), v128.load(b)), v128.load(c)) + ); + out += so; + a += sa; + b += sb; + c += sc; + } + return res; +} diff --git a/packages/simd/assembly/msubn.ts b/packages/simd/assembly/msubn.ts new file mode 100644 index 0000000000..619eeb8980 --- /dev/null +++ b/packages/simd/assembly/msubn.ts @@ -0,0 +1,23 @@ +export function msubn4_f32( + out: usize, + a: usize, + n: f32, + c: usize, + num: usize, + so: usize, + sa: usize, + sc: usize +): usize { + const res = out; + so <<= 2; + sa <<= 2; + sc <<= 2; + const vn = f32x4.splat(n); + for (; num-- > 0; ) { + v128.store(out, f32x4.sub(f32x4.mul(v128.load(a), vn), v128.load(c))); + out += so; + a += sa; + c += sc; + } + return res; +} diff --git a/packages/simd/assembly/mul.ts b/packages/simd/assembly/mul.ts new file mode 100644 index 0000000000..a0cc98151c --- /dev/null +++ b/packages/simd/assembly/mul.ts @@ -0,0 +1,21 @@ +export function mul4_f32( + out: usize, + a: usize, + b: usize, + num: usize, + so: usize, + sa: usize, + sb: usize +): usize { + so <<= 2; + sa <<= 2; + sb <<= 2; + const res = out; + for (; num-- > 0; ) { + v128.store(out, f32x4.mul(v128.load(a), v128.load(b))); + out += so; + a += sa; + b += sb; + } + return res; +} diff --git a/packages/simd/assembly/muln.ts b/packages/simd/assembly/muln.ts new file mode 100644 index 0000000000..fe51e65e26 --- /dev/null +++ b/packages/simd/assembly/muln.ts @@ -0,0 +1,19 @@ +export function muln4_f32( + out: usize, + a: usize, + n: f32, + num: usize, + so: usize, + sa: usize +): usize { + so <<= 2; + sa <<= 2; + const res = out; + const vn = f32x4.splat(n); + for (; num-- > 0; ) { + v128.store(out, f32x4.mul(v128.load(a), vn)); + out += so; + a += sa; + } + return res; +} diff --git a/packages/simd/assembly/mulv.ts b/packages/simd/assembly/mulv.ts new file mode 100644 index 0000000000..abfa0da04c --- /dev/null +++ b/packages/simd/assembly/mulv.ts @@ -0,0 +1,97 @@ +export function mul_m22v2_aos( + out: usize, + mat: usize, + vec: usize, + num: usize +): usize { + const res = out; + num >>= 1; + const m0 = v128.load(mat); + const m1 = v128.shuffle(m0, m0, 0, 1, 0, 1); + const m2 = v128.shuffle(m0, m0, 2, 3, 2, 3); + for (; num-- > 0; ) { + // v1xv1xv2xv2x * m.0101 + v1yv1yv2yv2y * m.2323 + const v = v128.load(vec); + v128.store( + out, + f32x4.add( + f32x4.mul(v128.shuffle(v, v, 0, 0, 2, 2), m1), + f32x4.mul(v128.shuffle(v, v, 1, 1, 3, 3), m2) + ) + ); + out += 16; + vec += 16; + } + return res; +} + +export function mul_m23v2_aos( + out: usize, + mat: usize, + vec: usize, + num: usize +): usize { + const res = out; + num >>= 1; + const m0 = v128.load(mat); + const m1 = v128.shuffle(m0, m0, 0, 1, 0, 1); + const m2 = v128.shuffle(m0, m0, 2, 3, 2, 3); + let m3 = v128.load(mat, 16); + m3 = v128.shuffle(m3, m3, 0, 1, 0, 1); + for (; num-- > 0; ) { + // v1xv1xv2xv2x * m.0101 + v1yv1yv2yv2y * m.2323 + m.4545 + const v = v128.load(vec); + v128.store( + out, + f32x4.add( + f32x4.add( + f32x4.mul(v128.shuffle(v, v, 0, 0, 2, 2), m1), + f32x4.mul(v128.shuffle(v, v, 1, 1, 3, 3), m2) + ), + m3 + ) + ); + out += 16; + vec += 16; + } + return res; +} + +export function mul_m44v4_aos( + out: usize, + mat: usize, + vec: usize, + num: usize, + so: usize, + sa: usize +): usize { + so <<= 2; + sa <<= 2; + const res = out; + const m0 = v128.load(mat); + const m1 = v128.load(mat, 16); + const m2 = v128.load(mat, 32); + const m3 = v128.load(mat, 48); + for (; num-- > 0; ) { + const v = v128.load(vec); + // v.xxxx * m.0123 + v.yyyy * m.4567 + v.zzzz * m.89ab + v.wwww * m.cdef + // TODO ryg's shuffle opt: + // https://fgiesen.wordpress.com/2015/02/05/a-small-note-on-simd-matrix-vector-multiplication/ + v128.store( + out, + f32x4.add( + f32x4.add( + f32x4.mul(v128.shuffle(v, v, 0, 0, 0, 0), m0), + f32x4.mul(v128.shuffle(v, v, 1, 1, 1, 1), m1) + ), + f32x4.add( + f32x4.mul(v128.shuffle(v, v, 2, 2, 2, 2), m2), + f32x4.mul(v128.shuffle(v, v, 3, 3, 3, 3), m3) + ) + ) + ); + out += so; + vec += sa; + } + return res; +} diff --git a/packages/simd/assembly/neg.ts b/packages/simd/assembly/neg.ts new file mode 100644 index 0000000000..ab0fde6f9a --- /dev/null +++ b/packages/simd/assembly/neg.ts @@ -0,0 +1,17 @@ +export function neg4_f32( + out: usize, + a: usize, + num: usize, + so: usize, + sa: usize +): usize { + so <<= 2; + sa <<= 2; + const res = out; + for (; num-- > 0; ) { + v128.store(out, f32x4.neg(v128.load(a))); + out += so; + a += sa; + } + return res; +} diff --git a/packages/simd/assembly/normalize.ts b/packages/simd/assembly/normalize.ts new file mode 100644 index 0000000000..d281afd64a --- /dev/null +++ b/packages/simd/assembly/normalize.ts @@ -0,0 +1,53 @@ +import { __magsq2, __magsq4 } from "./inline/magsq"; + +// @ts-ignore: decorator +@inline +function $norm(m: f32, norm: f32): f32 { + return m > f32.EPSILON ? norm / sqrt(m) : 1; +} + +export function normalize2_f32_aos( + out: usize, + a: usize, + num: usize, + norm: f32 +): usize { + const res = out; + num >>= 1; + for (; num-- > 0; ) { + const v = v128.load(a); + let vm = __magsq2(v); + vm = f32x4.replace_lane(vm, 0, $norm(f32x4.extract_lane(vm, 0), norm)); + vm = f32x4.replace_lane(vm, 2, $norm(f32x4.extract_lane(vm, 2), norm)); + v128.store(out, f32x4.mul(v, v128.shuffle(vm, vm, 0, 0, 2, 2))); + out += 16; + a += 16; + } + return res; +} + +export function normalize4_f32_aos( + out: usize, + a: usize, + num: usize, + norm: f32, + so: usize, + sa: usize +): usize { + so <<= 2; + sa <<= 2; + const res = out; + for (; num-- > 0; ) { + const v = v128.load(a); + const mag = __magsq4(v); + v128.store( + out, + mag > f32.EPSILON + ? f32x4.mul(v, f32x4.splat(norm / sqrt(mag))) + : v + ); + out += so; + a += sa; + } + return res; +} diff --git a/packages/simd/assembly/sqrt.ts b/packages/simd/assembly/sqrt.ts new file mode 100644 index 0000000000..4640d2acbd --- /dev/null +++ b/packages/simd/assembly/sqrt.ts @@ -0,0 +1,36 @@ +export function sqrt4_f32( + out: usize, + a: usize, + num: usize, + so: usize, + sa: usize +): usize { + so <<= 2; + sa <<= 2; + const res = out; + for (; num-- > 0; ) { + v128.store(out, f32x4.sqrt(v128.load(a))); + out += so; + a += sa; + } + return res; +} + +export function invsqrt4_f32( + out: usize, + a: usize, + num: usize, + so: usize, + sa: usize +): usize { + so <<= 2; + sa <<= 2; + const res = out; + const one = f32x4.splat(1); + for (; num-- > 0; ) { + v128.store(out, f32x4.div(one, f32x4.sqrt(v128.load(a)))); + out += so; + a += sa; + } + return res; +} diff --git a/packages/simd/assembly/sub.ts b/packages/simd/assembly/sub.ts new file mode 100644 index 0000000000..83af4ce826 --- /dev/null +++ b/packages/simd/assembly/sub.ts @@ -0,0 +1,21 @@ +export function sub4_f32( + out: usize, + a: usize, + b: usize, + num: usize, + so: usize, + sa: usize, + sb: usize +): usize { + so <<= 2; + sa <<= 2; + sb <<= 2; + const res = out; + for (; num-- > 0; ) { + v128.store(out, f32x4.sub(v128.load(a), v128.load(b))); + out += so; + a += sa; + b += sb; + } + return res; +} diff --git a/packages/simd/assembly/subn.ts b/packages/simd/assembly/subn.ts new file mode 100644 index 0000000000..888919b5f5 --- /dev/null +++ b/packages/simd/assembly/subn.ts @@ -0,0 +1,19 @@ +export function subn4_f32( + out: usize, + a: usize, + n: f32, + num: usize, + so: usize, + sa: usize +): usize { + so <<= 2; + sa <<= 2; + const res = out; + const vn = f32x4.splat(n); + for (; num-- > 0; ) { + v128.store(out, f32x4.sub(v128.load(a), vn)); + out += so; + a += sa; + } + return res; +} diff --git a/packages/simd/assembly/sum.ts b/packages/simd/assembly/sum.ts new file mode 100644 index 0000000000..47546880d2 --- /dev/null +++ b/packages/simd/assembly/sum.ts @@ -0,0 +1,11 @@ +import { __hadd4_f32 } from "./inline/hadd"; + +export function sum4_f32(a: usize, num: usize, sa: usize): f32 { + sa <<= 2; + let acc = f32x4.splat(0); + for (; num-- > 0; ) { + acc = f32x4.add(acc, v128.load(a)); + a += sa; + } + return __hadd4_f32(acc); +} diff --git a/packages/simd/assembly/swizzle.ts b/packages/simd/assembly/swizzle.ts new file mode 100644 index 0000000000..f2f0df84af --- /dev/null +++ b/packages/simd/assembly/swizzle.ts @@ -0,0 +1,65 @@ +/** + * Sets a single float vector lane `id` in `num` items from `addr`, + * spaced by `stride`. `id` is used as start offset for `addr`. Both + * `id` and `stride` are in floats, not bytes. Returns `addr`. + * + * ``` + * // set Y component in AOS vec4 buffer from addr 0x1000 + * set_lane_f32(0x1000, 1, 1.23, 4, 4) + * ``` + * + * @param addr + * @param id + * @param x + * @param num + * @param stride + */ +export function set_lane_f32( + addr: usize, + id: usize, + x: f32, + num: usize, + stride: usize +): usize { + const res = addr; + addr += id << 2; + stride <<= 2; + for (; num-- > 0; ) { + f32.store(addr, x); + addr += stride; + } + return res; +} + +export function swizzle4_32( + out: usize, + a: usize, + x: u32, + y: u32, + z: u32, + w: u32, + num: usize, + so: usize, + sa: usize +): usize { + const res = out; + so <<= 2; + sa <<= 2; + // create swizzle pattern from xyzw + // each lane: id * 0x04040404 + 0x00010203 + // TODO verify order + let mask = i64x2.replace_lane( + i64x2.splat(y * 0x0404040400000000 + x * 0x0000000004040404), + 1, + w * 0x0404040400000000 + z * 0x0000000004040404 + ); + mask = i64x2.add(mask, i32x4.splat(0x00010203)); + v128.store(out, mask); + + for (; num-- > 0; ) { + // v128.store(out, v128.swizzle(v128.load(a), mask)); + out += so; + a += sa; + } + return res; +} diff --git a/packages/simd/assembly/tsconfig.json b/packages/simd/assembly/tsconfig.json new file mode 100644 index 0000000000..20ee7a2111 --- /dev/null +++ b/packages/simd/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} \ No newline at end of file diff --git a/packages/simd/package.json b/packages/simd/package.json new file mode 100644 index 0000000000..65fef2c1ad --- /dev/null +++ b/packages/simd/package.json @@ -0,0 +1,56 @@ +{ + "name": "@thi.ng/simd", + "version": "0.1.0", + "description": "WASM based SIMD vector operations for batch processing", + "module": "./index.js", + "main": "./lib/index.js", + "umd:main": "./lib/index.umd.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/simd", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && yarn build:wasm && yarn build:es6 && node ../../scripts/bundle-module", + "build:release": "yarn clean && yarn build:wasm && yarn build:es6 && node ../../scripts/bundle-module all", + "build:es6": "tsc --declaration", + "build:test": "rimraf build && tsc -p test/tsconfig.json", + "build:wasm": "asc assembly/index.ts -b simd.wasm -t simd.wat --validate --optimize --enable simd --runtime none --importMemory --memoryBase 0 && yarn build:binary", + "build:binary": "echo \"export const BINARY = \\\"$(../../scripts/base64 simd.wasm)\\\";\" > src/binary.ts", + "test": "yarn build:test && node --experimental-wasm-simd build/test/index.js", + "cover": "yarn build:test && nyc node --experimental-wasm-simd build/test/*.js && nyc report --reporter=lcov", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib", + "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", + "pub": "yarn build:release && yarn publish --access public" + }, + "devDependencies": { + "@types/mocha": "^5.2.6", + "@types/node": "^12.6.3", + "assemblyscript": "AssemblyScript/assemblyscript", + "mocha": "^6.1.4", + "nyc": "^14.0.0", + "typedoc": "^0.15.0", + "typescript": "^3.6.4" + }, + "dependencies": { + "@thi.ng/checks": "^2.4.1", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/transducers-binary": "^0.4.6" + }, + "keywords": [ + "AssemblyScript", + "batch process", + "ES6", + "SIMD", + "typescript", + "vector", + "WASM" + ], + "publishConfig": { + "access": "public" + }, + "sideEffects": false +} diff --git a/packages/simd/src/api.ts b/packages/simd/src/api.ts new file mode 100644 index 0000000000..627965cc46 --- /dev/null +++ b/packages/simd/src/api.ts @@ -0,0 +1,223 @@ +export interface SIMD { + // prettier-ignore + abs4_f32(out: number, a: number, num: number, so: number, sa: number): number; + + // prettier-ignore + add4_f32(out: number, a: number, b: number, num: number, so: number, sa: number, sb: number): number; + + // prettier-ignore + addn4_f32(out: number, a: number, n: number, num: number, so: number, sa: number): number; + + // prettier-ignore + clamp4_f32(out: number, a: number, b: number, c: number, num: number, so: number, sa: number, sb: number, sc: number): number; + + // prettier-ignore + clampn4_f32(out: number, a: number, b: number, c: number, num: number, so: number, sa: number): number; + + // prettier-ignore + div4_f32(out: number, a: number, b: number, num: number, so: number, sa: number, sb: number): number; + + // prettier-ignore + divn4_f32(out: number, a: number, n: number, num: number, so: number, sa: number): number; + + /** + * Takes two densely packed vec2 AOS buffers `a` and `b`, computes + * their 2D dot products and stores results in `out`. Computes two + * results per iteration, hence `num` must be an even number or else + * the last vector will not be processed. + * + * `a` & `b` should be aligned to 16. + * + * @param out + * @param a + * @param b + * @param num + */ + // prettier-ignore + dot2_f32_aos(out: number, a: number, b: number, num: number): number; + + /** + * Takes two vec4 AOS buffers, computes their dot products and + * stores results in `out`. `so` should be 1 for packed result + * buffer. `sa` and `sb` indicate the stride lengths (in floats) + * between each vector in each respective buffer and should be a + * multiple of 4. + * + * @param out + * @param a + * @param b + * @param num + * @param so + * @param sa + * @param sb + */ + // prettier-ignore + dot4_f32_aos(out: number, a: number, b: number, num: number, so: number, sa: number, sb: number): number; + + /** + * Takes two vec4 SOA buffers and computes their 4D dot products and + * writes results to `out`. `sa` and `sb` indicate the element + * stride size (in floats) of the respective vectors (should be + * multiple of 4). The results are always stored in a packed layout. + * Processes 4 vectors per iteration, hence `num` should be a + * multiple of 4 too. + * + * @param out + * @param a + * @param b + * @param num + * @param sa + * @param sb + */ + // prettier-ignore + dot4_f32_soa(out: number, a: number, b: number, num: number, sa: number, sb: number): number; + + /** + * FIXME waiting for native impl of SIMD instr + * + * @see sqrt4_f32 + * + * @param out + * @param a + * @param num + * @param so + * @param sa + */ + // prettier-ignore + invsqrt4_f32(out: number, a: number, num: number, so: number, sa: number): number; + + /** + * Takes three vec4 buffers, computes componentwise `a * b + c` and + * stores results in `out`. Both AOS / SOA layouts are supported, as + * long as all buffers are using the same layout. + * + * All strides must by multiples of 4. All pointers should be + * aligned to multiples of 16. Returns `out` pointer. + * + * @param out + * @param a + * @param b + * @param c + * @param num number of vec4 + * @param so out element stride + * @param sa A element stride + * @param sb B element stride + * @param sc C element stride + */ + // prettier-ignore + madd4_f32(out: number, a: number, b: number, c: number, num: number, so: number, sa: number, sb: number, sc: number): number; + + // prettier-ignore + maddn4_f32(out: number, a: number, b: number, c: number, num: number, so: number, sa: number, sc: number): number; + + mag2_f32_aos(out: number, a: number, num: number): number; + + magsq2_f32_aos(out: number, a: number, num: number): number; + + // prettier-ignore + mag4_f32_aos( out: number, a: number, num: number, so: number, sa: number): number; + + // prettier-ignore + magsq4_f32_aos( out: number, a: number, num: number, so: number, sa: number): number; + + // prettier-ignore + max4_f32(out: number, a: number, b: number, num: number, so: number, sa: number, sb: number): number; + + // prettier-ignore + min4_f32(out: number, a: number, b: number, num: number, so: number, sa: number, sb: number): number; + + // prettier-ignore + mix4_f32(out: number, a: number, b: number, t: number, num: number, so: number, sa: number, sb: number, st: number): number; + + // prettier-ignore + mixn4_f32(out: number, a: number, b: number, t: number, num: number, so: number, sa: number, sb: number): number; + + // prettier-ignore + mul4_f32(out: number, a: number, b: number, num: number, so: number, sa: number, sb: number): number; + + // prettier-ignore + muln4_f32(out: number, a: number, n: number, num: number, so: number, sa: number): number; + + // prettier-ignore + mul_m22v2_aos(out: number, mat: number, vec: number, num: number): number; + + // prettier-ignore + mul_m23v2_aos(out: number, mat: number, vec: number, num: number): number; + + // prettier-ignore + mul_m44v4_aos(out: number, mat: number, vec: number, num: number, so: number, sv: number): number; + + // prettier-ignore + msub4_f32(out: number, a: number, b: number, c: number, num: number, so: number, sa: number, sb: number, sc: number): number; + + // prettier-ignore + msubn4_f32(out: number, a: number, b: number, c: number, num: number, so: number, sa: number, sc: number): number; + + // prettier-ignore + neg4_f32(out: number, a: number, num: number, so: number, sa: number): number; + + // prettier-ignore + normalize2_f32_aos(out: number, a: number, num: number, norm: number): number; + + // prettier-ignore + normalize4_f32_aos(out: number, a: number, num: number, norm: number, so: number, sa: number): number; + + /** + * FIXME waiting for native impl of SIMD instr + * + * @see invsqrt4_f32 + * + * @param out + * @param a + * @param num + * @param so + * @param sa + */ + // prettier-ignore + sqrt4_f32(out: number, a: number, num: number, so: number, sa: number): number; + + // prettier-ignore + sub4_f32(out: number, a: number, b: number, num: number, so: number, sa: number, sb: number): number; + + // prettier-ignore + subn4_f32(out: number, a: number, n: number, num: number, so: number, sa: number): number; + + sum4_f32(a: number, num: number, sa: number): number; + + /** + * WASM memory instance given to `init()`. + */ + memory: WebAssembly.Memory; + /** + * Float64 view of WASM memory. + */ + f64: Float64Array; + /** + * Float32 view of WASM memory. + */ + f32: Float32Array; + /** + * Uint32 view of WASM memory. + */ + u32: Uint32Array; + /** + * Int32 view of WASM memory. + */ + i32: Int32Array; + /** + * Uint16 of WASM memory. + */ + u16: Uint16Array; + /** + * Int16 view of WASM memory. + */ + i16: Int16Array; + /** + * Uint8 view of WASM memory. + */ + u8: Uint8Array; + /** + * Int8 view of WASM memory. + */ + i8: Int8Array; +} diff --git a/packages/simd/src/index.ts b/packages/simd/src/index.ts new file mode 100644 index 0000000000..c1373513fb --- /dev/null +++ b/packages/simd/src/index.ts @@ -0,0 +1,52 @@ +import { base64Decode } from "@thi.ng/transducers-binary"; +import { SIMD } from "./api"; +import { BINARY } from "./binary"; + +/** + * Creates a new WASM module instance w/ user supplied memory. The WASM + * module itself doesn't use any memory itself and the full address + * space of the given memory instance is freely available for use. The + * returned object exposes all SIMD functions defined by this package, + * as well as various views of the provided memory. See `SIMD` interface + * in api.ts for details. + * + * The initialization method used here is synchronous and the WASM + * binary is inlined as base64 string in this package (The `binary.ts` + * file is generated during build time). + * + * ``` + * // create instance w/ 1MB memory (16 * 64KB) + * simd = init(new WebAssembly.Memory({ initial: 16 })); + * ``` + * + * Function will throw an error if WASM is unavailable or underlying + * runtime doesn't yet support SIMD instructions. + * + * @param memory + */ +export const init = (memory: WebAssembly.Memory): SIMD | undefined => { + const buf = memory.buffer; + return { + ...new WebAssembly.Instance( + new WebAssembly.Module(new Uint8Array([...base64Decode(BINARY)])), + { + env: { + memory, + abort(_: any, file: any, line: number, column: number) { + console.error( + `abort called in ${file}: ${line}:${column}` + ); + } + } + } + ).exports, + f32: new Float32Array(buf), + f64: new Float64Array(buf), + u32: new Uint32Array(buf), + i32: new Int32Array(buf), + u16: new Uint16Array(buf), + i16: new Int16Array(buf), + u8: new Uint8Array(buf), + i8: new Int8Array(buf) + }; +}; diff --git a/packages/simd/test/index.ts b/packages/simd/test/index.ts new file mode 100644 index 0000000000..50224e117f --- /dev/null +++ b/packages/simd/test/index.ts @@ -0,0 +1,281 @@ +import { equiv } from "@thi.ng/equiv"; +import { eqDelta } from "@thi.ng/vectors"; +import { init } from "../src"; + +const simd = init(new WebAssembly.Memory({ initial: 1 }))!; +console.log(simd); + +const assertEqual = (res: any, exp: any, msg?: string) => { + if (!equiv(res, exp)) { + process.stderr.write(msg || `expected: ${exp}, got ${res}\n\n`); + process.exit(1); + } +}; + +const assertEqualDelta = (res: any, exp: any, eps?: number, msg?: string) => { + if (!eqDelta(res, exp, eps)) { + process.stderr.write(msg || `expected: ${exp}, got ${res}\n\n`); + process.exit(1); + } +}; + +const res_f32 = (addr: number, n: number) => + simd.f32.slice(addr / 4, addr / 4 + n); + +// basic math ops +// prettier-ignore +simd.f32.set([ + // a + 1, 2, 3, 4, 5, 6, 7, 8, + // gap + 0, 0, 0, 0, + //b + 10, 20, 30, 40, 50, 60, 70, 80 +]); +// add4_f32 +simd.add4_f32(1024, 0, 48, 2, 4, 4, 4); +assertEqual(res_f32(1024, 8), [11, 22, 33, 44, 55, 66, 77, 88]); +// addn4_f32 +simd.addn4_f32(1024, 0, 10, 2, 4, 4); +assertEqual(res_f32(1024, 8), [11, 12, 13, 14, 15, 16, 17, 18]); +// sub4_f32 +simd.sub4_f32(1024, 0, 48, 2, 4, 4, 4); +assertEqual(res_f32(1024, 8), [-9, -18, -27, -36, -45, -54, -63, -72]); +// subn4_f32 +simd.subn4_f32(1024, 0, 10, 2, 4, 4); +assertEqual(res_f32(1024, 8), [-9, -8, -7, -6, -5, -4, -3, -2]); +// mul4_f32 +simd.mul4_f32(1024, 0, 48, 2, 4, 4, 4); +assertEqual(res_f32(1024, 8), [10, 40, 90, 160, 250, 360, 490, 640]); +// muln4_f32 +simd.muln4_f32(1024, 0, 10, 2, 4, 4); +assertEqual(res_f32(1024, 8), [10, 20, 30, 40, 50, 60, 70, 80]); +// div4_f32 +// simd.div4_f32(1024, 0, 48, 2, 4, 4, 4); +// assertEqualDelta(res_f32(1024, 8), [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]); +// divn4_f32 +// simd.divn4_f32(1024, 0, 10, 2, 4, 4); +// assertEqualDelta(res_f32(1024, 8), [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]); + +// abs4_f32 +simd.f32.set([-1, 2, -3, 4, 5, -6, 7, -8]); +simd.abs4_f32(1024, 0, 2, 4, 4); +assertEqual(res_f32(1024, 8), [1, 2, 3, 4, 5, 6, 7, 8]); +// neg4_f32 +simd.neg4_f32(1024, 0, 2, 4, 4); +assertEqual(res_f32(1024, 8), [1, -2, 3, -4, -5, 6, -7, 8]); + +// clamp4_f32 +// prettier-ignore +simd.f32.set([ + // a + -10, 10, -10, 10, + 10, -10, 10, -10, + // min + -1, -2, -3, -4, + // max + 1, 2, 3, 4 +]); +simd.clamp4_f32(1024, 0, 32, 48, 2, 4, 4, 0, 0); +assertEqual(res_f32(1024, 8), [-1, 2, -3, 4, 1, -2, 3, -4]); +//clampn4_f32 +simd.clampn4_f32(1024, 0, -1, 1, 2, 4, 4); +assertEqual(res_f32(1024, 8), [-1, 1, -1, 1, 1, -1, 1, -1]); +//min4_f32 +simd.min4_f32(1024, 0, 32, 2, 4, 4, 0); +assertEqual(res_f32(1024, 8), [-10, -2, -10, -4, -1, -10, -3, -10]); +simd.min4_f32(1024, 0, 48, 2, 4, 4, 0); +assertEqual(res_f32(1024, 8), [-10, 2, -10, 4, 1, -10, 3, -10]); +//max4_f32 +simd.max4_f32(1024, 0, 32, 2, 4, 4, 0); +assertEqual(res_f32(1024, 8), [-1, 10, -3, 10, 10, -2, 10, -4]); +simd.max4_f32(1024, 0, 48, 2, 4, 4, 0); +assertEqual(res_f32(1024, 8), [1, 10, 3, 10, 10, 2, 10, 4]); + +// dot2_aos +// prettier-ignore +simd.f32.set([ + // a + 1, 2, 3, 4, + // b + 10, 20, 30, 40 + ]); +simd.dot2_f32_aos(1024, 0, 16, 2); +assertEqual(res_f32(1024, 2), [50, 250]); + +// dot4_aos +// prettier-ignore +simd.f32.set([ + // a + 1, 2, 3, 4, 5, 6, 7, 8, + // b + 10, 20, 30, 40, 50, 60, 70, 80 + ]); +simd.dot4_f32_aos(1024, 0, 32, 2, 1, 4, 4); +assertEqual(res_f32(1024, 2), [300, 1740]); + +// dot4_soa +// prettier-ignore +simd.f32.set([ + // ax + 1, 2, 3, 4, + // ay + 1, 2, 3, 4, + // az + 1, 2, 3, 4, + // aw + 1, 2, 3, 4, + // bx + 10, 10, 10, 10, + // by + 20, 20, 20, 20, + // bz + 30, 30, 30, 30, + // bw + 40, 40, 40, 40 + ]); +simd.dot4_f32_soa(1024, 0, 64, 4, 4, 4); +assertEqual(res_f32(1024, 4), [100, 200, 300, 400]); + +// madd4 +// prettier-ignore +simd.f32.set([ + // a + 1, 2, 3, 4, 5, 6, 7, 8, + // b + 11, 11, 11, 11, 11, 11, 11, 11, + // c + 100, 200, 300, 400, 500, 600, 700, 800 + ]); +simd.madd4_f32(1024, 0, 32, 64, 2, 4, 4, 4, 4); +assertEqual(res_f32(1024, 8), [111, 222, 333, 444, 555, 666, 777, 888]); +// msub4 +simd.msub4_f32(1024, 0, 32, 64, 2, 4, 4, 4, 4); +// prettier-ignore +assertEqual(res_f32(1024, 8), [-89, -178, -267, -356, -445, -534, -623, -712]); + +// maddn4 +// prettier-ignore +simd.f32.set([ + // a + 1, 2, 3, 4, 5, 6, 7, 8, + // c + 100, 200, 300, 400, 500, 600, 700, 800 + ]); +simd.maddn4_f32(1024, 0, 11, 32, 2, 4, 4, 4); +// prettier-ignore +assertEqual(res_f32(1024, 8), [111, 222, 333, 444, 555, 666, 777, 888]); +// msubn4 +simd.msubn4_f32(1024, 0, 11, 32, 2, 4, 4, 4); +// prettier-ignore +assertEqual(res_f32(1024, 8), [-89, -178, -267, -356, -445, -534, -623, -712]); + +// magsq2 +// magsq4 +simd.f32.set([1, 2, 10, 20, -100, 200, 100, -200]); +simd.magsq2_f32_aos(1024, 0, 4); +assertEqualDelta(res_f32(1024, 4), [ + 1 * 1 + 2 * 2, + 10 * 10 + 20 * 20, + 100 * 100 + 200 * 200, + 100 * 100 + 200 * 200 +]); +simd.mag2_f32_aos(1024, 0, 4); +assertEqualDelta(res_f32(1024, 4), [ + Math.sqrt(5), + Math.sqrt(500), + Math.sqrt(50000), + Math.sqrt(50000) +]); + +simd.magsq4_f32_aos(1024, 0, 2, 1, 4); +assertEqualDelta(res_f32(1024, 2), [505, 100000]); +simd.mag4_f32_aos(1024, 0, 2, 1, 4); +assertEqualDelta(res_f32(1024, 2), [Math.sqrt(505), Math.sqrt(100000)]); + +// mix4_f32 +// mixn4_f32 +// prettier-ignore +simd.f32.set([ + // a + 1, 2, 3, 4, 5, 6, 7, 8, + // b + 10, 20, 30, 40, 50, 60, 70, 80, + // t + 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8 +]); +simd.mix4_f32(1024, 0, 32, 64, 2, 4, 4, 4, 4); +// prettier-ignore +assertEqualDelta( + res_f32(1024, 8), + [1.9, 5.6, 11.1, 18.4, 27.5, 38.4, 51.1, 65.6] +); +simd.mixn4_f32(1024, 0, 32, 0.5, 2, 4, 4, 4); +// prettier-ignore +assertEqualDelta( + res_f32(1024, 8), + [5.5, 11, 16.5, 22, 27.5, 33, 38.5, 44] +); + +// mul_m22v2_aos +// mul_m23v2_aos +// prettier-ignore +simd.f32.set([ + // mat23 (col major) + 10, 0, + 0, 20, + 100, 200, + // space + 0, 0, + // 4x vec2 + 1, 2, + 3, 4, + 5, 6, + -1, -1 + ]); +simd.mul_m22v2_aos(1024, 0, 32, 4); +assertEqual(res_f32(1024, 8), [10, 40, 30, 80, 50, 120, -10, -20]); +simd.mul_m23v2_aos(1024, 0, 32, 4); +assertEqual(res_f32(1024, 8), [110, 240, 130, 280, 150, 320, 90, 180]); + +// mul_m44v4_aos +// prettier-ignore +simd.f32.set([ + // mat4 (col major) + 10, 0, 0, 0, + 0, 20, 0, 0, + 0, 0, 30, 0, + 100, 200, 300, 1, + // vec4 + 1, 2, 3, 1, + 4, 5, 6, 1, + ]); +simd.mul_m44v4_aos(1024, 0, 64, 2, 4, 4); +assertEqual(res_f32(1024, 8), [110, 240, 390, 1, 140, 300, 480, 1]); + +// normalize2_f32 +simd.f32.set([-1, 1, 0, 2, -1e-8, 0, 4, -2]); +simd.normalize2_f32_aos(1024, 0, 4, 10); +assertEqualDelta( + res_f32(1024, 8), + [-7.07, 7.07, 0, 10, 0, 0, 8.94, -4.47], + 0.01 +); +// normalize4_f32 +simd.f32.set([1, 0, -1, 0, 0, -1, 0, 1, -1, 1, 1, -1, 1e-8, -1e-6, 1e-8, 1e-6]); +simd.normalize4_f32_aos(1024, 0, 4, 10, 4, 4); +assertEqualDelta( + res_f32(1024, 16), + // prettier-ignore + [ + 7.07, 0, -7.07, 0, + 0, -7.07, 0, 7.07, + -5, 5, 5, -5, + 0, 0, 0, 0, + ], + 0.01 +); + +// prettier-ignore +simd.f32.set([1, 2, 3, 4, 5, 6, 7, 8, 10, 20, 30, 40, 50, 60, 70, 80]); +assertEqual(simd.sum4_f32(0, 4, 4), 396); diff --git a/packages/simd/test/tsconfig.json b/packages/simd/test/tsconfig.json new file mode 100644 index 0000000000..f6e63560dd --- /dev/null +++ b/packages/simd/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build", + "module": "commonjs" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/simd/tsconfig.json b/packages/simd/tsconfig.json new file mode 100644 index 0000000000..893b9979c5 --- /dev/null +++ b/packages/simd/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "module": "es6", + "target": "es6" + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/soa/.npmignore b/packages/soa/.npmignore new file mode 100644 index 0000000000..24f388daa6 --- /dev/null +++ b/packages/soa/.npmignore @@ -0,0 +1,18 @@ +.cache +.meta +.nyc_output +*.gz +*.html +*.svg +*.tgz +*.h +*.o +*.wasm +build +coverage +dev +doc +export +src* +test +tsconfig.json diff --git a/packages/soa/CHANGELOG.md b/packages/soa/CHANGELOG.md new file mode 100644 index 0000000000..d957fd1047 --- /dev/null +++ b/packages/soa/CHANGELOG.md @@ -0,0 +1,23 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# 0.1.0 (2019-11-09) + + +### Bug Fixes + +* **soa:** remove obsolete imports ([2309ccd](https://github.com/thi-ng/umbrella/commit/2309ccd6e581b6f385f4a2720fd2ad5cfb8a0d79)) + + +### Features + +* **soa:** add new pkg [@thi](https://github.com/thi).ng/soa ([5f8ffa1](https://github.com/thi-ng/umbrella/commit/5f8ffa175fabc4518f6b931c8c57473ea8ab1a74)) +* **soa:** add/update types, update aos(), add SOA.setValues(), tests ([b8e0780](https://github.com/thi-ng/umbrella/commit/b8e07806427041a7ef3413ca47357e3360f6a4c8)) +* **soa:** update SOAAttribSpec.buf to use ArrayBuffer w/ opt offset ([2759570](https://github.com/thi-ng/umbrella/commit/27595700ce0df21258dad58e18abf98b8ddb7c30)) + + +### Performance Improvements + +* **soa:** update attribValues() impl ([786a02f](https://github.com/thi-ng/umbrella/commit/786a02f66fd0f50e678f3eb048964fadf293db3f)) diff --git a/packages/soa/LICENSE b/packages/soa/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/soa/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/soa/README.md b/packages/soa/README.md new file mode 100644 index 0000000000..cd1940c29c --- /dev/null +++ b/packages/soa/README.md @@ -0,0 +1,64 @@ +# @thi.ng/soa + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/soa.svg)](https://www.npmjs.com/package/@thi.ng/soa) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/soa.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + +- [About](#about) + - [Status](#status) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Related packages](#related-packages) +- [Usage examples](#usage-examples) +- [Authors](#authors) +- [License](#license) + + + +## About + +SOA & AOS memory layouts, memory mapped lenses, optional & extensible +serialization. + +### Status + +ALPHA - WIP - See tests for usage. This package might be merged with +and/or superseded by +[@thi.ng/ecs](https://github.com/thi-ng/umbrella/tree/master/packages/ecs). + +## Installation + +```bash +yarn add @thi.ng/soa +``` + +## Dependencies + +- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) +- [@thi.ng/binary](https://github.com/thi-ng/umbrella/tree/master/packages/binary) +- [@thi.ng/transducers-binary](https://github.com/thi-ng/umbrella/tree/master/packages/transducers-binary) +- [@thi.ng/vectors](https://github.com/thi-ng/umbrella/tree/master/packages/vectors) + +## Related packages + +- [@thi.ng/unionstruct](https://github.com/thi-ng/umbrella/tree/master/packages/unionstruct) +- [@thi.ng/vector-pools](https://github.com/thi-ng/umbrella/tree/master/packages/vector-pools) (possibly will be deprecated / merged) + +## Usage examples + +```ts +import * as s from "@thi.ng/soa"; +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2019 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/soa/package.json b/packages/soa/package.json new file mode 100644 index 0000000000..397175a69a --- /dev/null +++ b/packages/soa/package.json @@ -0,0 +1,60 @@ +{ + "name": "@thi.ng/soa", + "version": "0.1.0", + "description": "SOA & AOS memory mapped structured lenses with optional & extensible serialization", + "module": "./index.js", + "main": "./lib/index.js", + "umd:main": "./lib/index.umd.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/soa", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module", + "build:release": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module all", + "build:es6": "tsc --declaration", + "build:test": "rimraf build && tsc -p test/tsconfig.json", + "test": "yarn build:test && mocha build/test/*.js", + "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib", + "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", + "pub": "yarn build:release && yarn publish --access public" + }, + "devDependencies": { + "@thi.ng/equiv": "^1.0.10", + "@types/mocha": "^5.2.6", + "@types/node": "^12.6.3", + "mocha": "^6.1.4", + "nyc": "^14.0.0", + "typedoc": "^0.15.0", + "typescript": "^3.6.4" + }, + "dependencies": { + "@thi.ng/api": "^6.5.0", + "@thi.ng/binary": "^1.1.1", + "@thi.ng/transducers-binary": "^0.4.6", + "@thi.ng/vectors": "^4.0.0" + }, + "keywords": [ + "AOS", + "ES6", + "interop", + "memory mapped", + "serialize", + "shared memory", + "SOA", + "struct", + "typedarray", + "typescript", + "WASM", + "WebGL" + ], + "publishConfig": { + "access": "public" + }, + "sideEffects": false +} diff --git a/packages/soa/src/aos.ts b/packages/soa/src/aos.ts new file mode 100644 index 0000000000..22a0e7e0e6 --- /dev/null +++ b/packages/soa/src/aos.ts @@ -0,0 +1,71 @@ +import { SIZEOF } from "@thi.ng/api"; +import { align, Pow2 } from "@thi.ng/binary"; +import { AOSSpecs, SOASpecs } from "./api"; +import { SOA } from "./soa"; +import { prepareSpec } from "./utils"; + +/** + * Constructs SOA instance from given attrib specs and optional + * ArrayBuffer (w/ optional start address / byte offset, which MUST be + * properly pre-aligned). The resulting layout will be an underlying + * interleaved AOS buffer with each attrib configured to map that same + * array buffer relative to the given `byteOffset` (default: 0). If no + * array buffer is given, a properly sized one will be created. + * + * First computes attrib offsets, alignments and the overall struct + * size, then configures buffer views and strides for each attrib. This + * is to ensure each attrib is correctly mapped in its buffer view (e.g. + * float values need to be aligned to 4-byte boundaries). The overall + * inter-struct packing/stride length is dependent on the largest attrib + * type used. E.g. the following specs will cause a stride length of 16 + * bytes between each resulting SOA element, even though the actual + * struct size is only 13 bytes: + * + * ``` + * aos( + * 1024, + * { + * a: { type: Type.U16, size: 1 }, // 2 bytes, align 2, offset 0 + * b: { type: Type.F32, size: 2 }, // 8 bytes, align 4, offset 4 + * c: { type: Type.U8, size: 1 }, // 1 byte, align 1, offset 12 + * } + * ); + * ``` + * + * @param num + * @param specs + * @param buf + * @param byteOffset + */ +export const aos = ( + num: number, + specs: AOSSpecs, + buf?: ArrayBuffer, + byteOffset = 0 +) => { + let total = 0; + let maxSize = 0; + const offsets = >{}; + const soaSpecs = >{}; + for (let id in specs) { + const spec = prepareSpec(specs[id]); + const tsize = SIZEOF[spec.type!]; + maxSize = Math.max(maxSize, tsize); + // align field to type size + total = align(total, tsize); + offsets[id] = total; + total += tsize * spec.size!; + soaSpecs[id] = spec; + } + // align total struct size to largest type + total = align(total, maxSize); + buf = buf || new ArrayBuffer(total * num + byteOffset); + for (let id in soaSpecs) { + const spec = soaSpecs[id]; + const tsize = SIZEOF[spec.type!]; + spec.stride = total / tsize; + spec.buf = buf; + spec.byteOffset = byteOffset + offsets[id]; + } + return new SOA(num, soaSpecs); +}; diff --git a/packages/soa/src/api.ts b/packages/soa/src/api.ts new file mode 100644 index 0000000000..510c99fd27 --- /dev/null +++ b/packages/soa/src/api.ts @@ -0,0 +1,75 @@ +import { Fn, Type } from "@thi.ng/api"; +import { ReadonlyVec, Vec } from "@thi.ng/vectors"; + +export interface AOSAttribSpec { + /** + * Element size + */ + size: number; + /** + * Primitive type ID for backing array. + * Default: Type.F32 + */ + type: Type; + /** + * Default value + */ + default: ReadonlyVec; +} + +export interface SOAAttribSpec extends AOSAttribSpec { + /** + * Optional user supplied backing buffer. + */ + buf: ArrayBuffer; + /** + * Optional start offset for mapped region in backing buffer. Only + * used if `buf` is given. + */ + byteOffset: number; + /** + * Number of indices between each SOA value. + * MUST be >= size + * Default: size + */ + stride: number; +} + +/** + * Alias for main config object of attribute specs. Declares an object + * of partial SOAAttribSpec values (i.e. every item is made optional) + */ +export type AOSSpecs = Record>; + +/** + * Alias for main config object of attribute specs. Declares an object + * of partial SOAAttribSpec values (i.e. every item is made optional) + */ +export type SOASpecs = Record>; + +/** + * Mapped type for index lookups. Declares an object with all keys from + * given `AttribSpecs` and each value a `Vec`. + */ +export type SOATuple = Record; + +export interface SerializerPair { + decode: (v: ReadonlyVec) => T; + encode: (x: T) => ReadonlyVec; +} + +export type SerializerSpecs = Record>; + +export type SerializedType< + T extends SerializerSpecs, + K extends keyof T +> = ReturnType; + +export type SerializedTuple = { + [P in keyof T]: SerializedType; +}; + +export interface Serializer { + decode: Fn, SerializedTuple>; + encode: Fn>, Partial>>; +} diff --git a/packages/soa/src/index.ts b/packages/soa/src/index.ts new file mode 100644 index 0000000000..d6ee8f2185 --- /dev/null +++ b/packages/soa/src/index.ts @@ -0,0 +1,5 @@ +export * from "./api"; + +export * from "./aos"; +export * from "./soa"; +export * from "./serialize"; diff --git a/packages/soa/src/serialize.ts b/packages/soa/src/serialize.ts new file mode 100644 index 0000000000..eee7aafe77 --- /dev/null +++ b/packages/soa/src/serialize.ts @@ -0,0 +1,74 @@ +import { assert } from "@thi.ng/api"; +import { utf8Decode, utf8Encode } from "@thi.ng/transducers-binary"; +import { Vec } from "@thi.ng/vectors"; +import { + SerializedTuple, + Serializer, + SerializerPair, + SerializerSpecs +} from "./api"; + +/** + * Identity serializer pair (no op). + */ +export const ident = >{ + decode: (x) => x, + encode: (x) => x +}; + +/** + * Serializer pair for scalars. + */ +export const scalar = >{ + decode: (v) => v[0], + encode: (x) => [x] +}; + +const toUTF8 = utf8Decode(); + +/** + * Zero-terminated UTF-8 string serializer pair for given max length + * (incl. final \0 char). + * + * `write()` throws error if resulting byte sequence is larger than + * configured `maxLen`. + * + * @param maxLen + */ +export const utf8z = (maxLen: number) => + >{ + decode: (v) => { + let acc = ""; + const xf = toUTF8([null, null, (_, x) => (acc += x)])[2]; + for (let i = 0, n = v.length; i < n; i++) { + const c = v[i]; + if (c === 0) break; + xf(acc, c); + } + return acc; + }, + encode: (v: string) => { + const bytes = [...utf8Encode(v), 0]; + assert(bytes.length <= maxLen, `string too large: "${v}"`); + return bytes; + } + }; + +export const serializer = ( + specs: T +): Serializer => ({ + decode(tuple) { + const res = >{}; + for (let id in tuple) { + res[id] = specs[id].decode(tuple[id]); + } + return res; + }, + encode: (tuple) => { + const res = >>{}; + for (let id in tuple) { + res[id] = specs[id].encode(tuple[id]); + } + return res; + } +}); diff --git a/packages/soa/src/soa.ts b/packages/soa/src/soa.ts new file mode 100644 index 0000000000..0910ad6b68 --- /dev/null +++ b/packages/soa/src/soa.ts @@ -0,0 +1,207 @@ +import { + assert, + ILength, + SIZEOF, + TypedArray, + typedArray, + TYPEDARRAY_CTORS +} from "@thi.ng/api"; +import { ReadonlyVec, Vec } from "@thi.ng/vectors"; +import { SOAAttribSpec, SOASpecs, SOATuple } from "./api"; +import { prepareSpec } from "./utils"; + +export class SOA implements ILength { + length: number; + buffers: Record; + specs: SOASpecs; + + constructor(num: number, specs: SOASpecs) { + this.length = num; + this.buffers = >{}; + this.specs = >{}; + this.addSpecs(specs); + } + + keys() { + return Object.keys(this.specs); + } + + *values(from = 0, to = this.length): IterableIterator> { + this.ensureIndex(from); + this.ensureIndex(to, from, this.length); + for (; from < to; from++) { + yield this.indexUnsafe(from); + } + } + + attribValues(id: K, from = 0, to = this.length) { + this.ensureAttrib(id); + this.ensureIndex(from); + this.ensureIndex(to, from, this.length); + let { size, stride, type } = this.specs[id]; + const ctor = TYPEDARRAY_CTORS[type!]; + const buf = this.buffers[id].buffer; + stride! *= SIZEOF[type!]; + from *= stride!; + to *= stride!; + const res: Vec[] = []; + for (; from < to; from += stride!) { + res.push(new ctor(buf, from, size!)); + } + return res; + } + + attribValue(id: K, i: number): Vec { + this.ensureAttrib(id); + this.ensureIndex(i); + return this.attribValueUnsafe(id, i); + } + + attribValueUnsafe(id: K, i: number): Vec { + const spec = this.specs[id]; + i *= spec.stride!; + return this.buffers[id].subarray(i, i + spec.size!); + } + + setAttribValue(id: K, i: number, val: ReadonlyVec) { + this.ensureAttrib(id); + this.ensureIndex(i); + const spec = this.specs[id]; + assert(val.length <= spec.size!, `${id} value too large`); + this.buffers[id].set(val, i * spec.stride!); + } + + setAttribValueUnsafe(id: K, i: number, val: ReadonlyVec) { + this.buffers[id].set(val, i * this.specs[id].stride!); + return this; + } + + setAttribValues(id: K, vals: Iterable, from = 0) { + this.ensureAttrib(id); + this.ensureIndex(from); + const buf = this.buffers[id]; + const stride = this.specs[id].stride!; + const end = this.length * stride; + let i = from * stride; + for (let v of vals) { + buf.set(v, i); + i += stride; + if (i >= end) break; + } + return this; + } + + index(i: number): SOATuple; + index(i: number, ids: ID[]): SOATuple; + index(i: number, ids?: K[]): any { + this.ensureIndex(i); + return this.indexUnsafe(i, ids!); + } + + indexUnsafe(i: number): SOATuple; + indexUnsafe(i: number, ids: ID[]): SOATuple; + indexUnsafe(i: number, ids?: K[]): any { + const res = >{}; + if (ids) { + for (let i = ids.length; --i >= 0; ) { + const id = ids[i]; + res[id] = this.attribValueUnsafe(id, i); + } + } else { + for (let id in this.specs) { + res[id] = this.attribValueUnsafe(id, i); + } + } + return res; + } + + setIndex(i: number, vals: Partial>) { + this.ensureIndex(i); + for (let id in vals) { + this.setAttribValue(id, i, vals[id]!); + } + return this; + } + + setIndexUnsafe(i: number, vals: Partial>) { + for (let id in vals) { + this.setAttribValueUnsafe(id, i, vals[id]!); + } + return this; + } + + setValues(vals: Partial>>, from = 0) { + for (let id in vals) { + this.setAttribValues(id, vals[id]!, from); + } + return this; + } + + copyTo( + dest: SOA, + ids?: K[], + destFrom = 0, + srcFrom = 0, + srcTo = this.length + ) { + this.ensureIndex(srcFrom); + this.ensureIndex(srcTo, srcFrom, this.length); + const num = srcTo - srcFrom; + dest.ensureIndex(destFrom); + dest.ensureIndex(destFrom + num, destFrom, dest.length); + ids = ids || Object.keys(this.specs); + for (let k = ids.length; --k >= 0; ) { + const id = ids[k]; + for (let i = srcFrom, j = destFrom; i < srcTo; i++, j++) { + dest.setAttribValueUnsafe(id, j, this.attribValueUnsafe(id, i)); + } + } + return dest; + } + + addSpecs(specs: SOASpecs) { + const num = this.length; + for (let id in specs) { + assert(!this.specs[id], `attrib ${id} already exists`); + const spec = prepareSpec(specs[id]); + this.validateSpec(id, spec); + const { stride, default: defVal } = spec; + const buffer = spec.buf + ? typedArray(spec.type!, spec.buf, spec.byteOffset || 0) + : typedArray(spec.type!, num * stride!); + if (defVal) { + for (let i = 0; i < num; i++) { + buffer.set(defVal, i * stride!); + } + } + this.specs[id] = spec; + this.buffers[id] = buffer; + } + } + + protected validateSpec(id: K, spec: Partial) { + assert(spec.stride! >= spec.size!, `${id} illegal stride`); + assert( + !spec.buf || + spec.buf.byteLength >= + ((this.length - 1) * spec.stride! + spec.size!) * + SIZEOF[spec.type!], + `${id} buffer too small` + ); + assert( + spec.default === undefined || spec.default.length === spec.size, + `illegal default value for ${id}, expected size: ${spec.size}` + ); + } + + protected ensureAttrib(id: K) { + assert(!!this.specs[id], `invalid attrib ${id}`); + } + + protected ensureIndex(i: number, min = 0, max = this.length - 1) { + assert(i >= min && i <= max, `index out of bounds: ${i}`); + } +} + +export const soa = (num: number, specs: SOASpecs) => + new SOA(num, specs); diff --git a/packages/soa/src/utils.ts b/packages/soa/src/utils.ts new file mode 100644 index 0000000000..a5bcdc3f98 --- /dev/null +++ b/packages/soa/src/utils.ts @@ -0,0 +1,8 @@ +import { Type } from "@thi.ng/api"; +import { SOAAttribSpec } from "./api"; + +export const prepareSpec = (spec: Partial) => { + spec = { type: Type.F32, size: 1, ...spec }; + !spec.stride && (spec.stride = spec.size); + return spec; +}; diff --git a/packages/soa/test/aos.ts b/packages/soa/test/aos.ts new file mode 100644 index 0000000000..bf7b6e93c2 --- /dev/null +++ b/packages/soa/test/aos.ts @@ -0,0 +1,47 @@ +import { Type } from "@thi.ng/api"; +import { equiv } from "@thi.ng/equiv"; +import * as assert from "assert"; +import { aos } from "../src"; + +describe("aos", () => { + it("basic", () => { + const struct = aos( + 2, + { + a: { type: Type.U16, size: 1 }, // 2, 0 + b: { type: Type.F32, size: 2 }, // 8, 4 + c: { type: Type.U8, size: 1 } // 1, 12 + }, + undefined, + 0x100 + ); + assert( + struct.buffers.a.buffer === struct.buffers.b.buffer && + struct.buffers.b.buffer === struct.buffers.c.buffer + ); + assert.equal(struct.specs.a.stride, 8); + assert.equal(struct.specs.b.stride, 4); + assert.equal(struct.specs.c.stride, 16); + assert.equal(struct.buffers.a.byteOffset, 0x100); + assert.equal(struct.buffers.b.byteOffset, 0x104); + assert.equal(struct.buffers.c.byteOffset, 0x10c); + assert.equal(struct.buffers.a.buffer.byteLength, 0x100 + 0x20); + struct.setValues({ + a: [[0x1001], [0x2002]], + b: [[1, 2], [3, 4]], + c: [[0xff], [0xfe]] + }); + assert( + equiv( + [...struct.values()], + [ + { a: [0x1001], b: [1, 2], c: [0xff] }, + { a: [0x2002], b: [3, 4], c: [0xfe] } + ] + ) + ); + const x = struct.index(1); + x.a[0] = 0xaa55; + assert.equal(struct.buffers.a[8], 0xaa55); + }); +}); diff --git a/packages/soa/test/serialize.ts b/packages/soa/test/serialize.ts new file mode 100644 index 0000000000..f85db51c46 --- /dev/null +++ b/packages/soa/test/serialize.ts @@ -0,0 +1,48 @@ +import { Type } from "@thi.ng/api"; +import * as assert from "assert"; +import { + scalar, + serializer, + soa, + utf8z +} from "../src"; + +describe("serialize", () => { + it("scalar", () => { + const struct = soa(2, { id: { type: Type.U32, size: 1 } }); + const ser = serializer({ id: scalar }); + struct.setIndex(0, ser.encode({ id: 0xdecafbad })); + struct.setIndex(1, ser.encode({ id: 0xaa55aa55 })); + assert.deepEqual([...struct.values()].map(ser.decode), [ + { id: 0xdecafbad }, + { id: 0xaa55aa55 } + ]); + }); + + it("utf8z", () => { + const struct = soa(2, { name: { type: Type.U8, size: 10 } }); + const ser = serializer({ name: utf8z(10) }); + assert.deepEqual(ser.decode(struct.index(0)), { name: "" }); + struct.setIndex(0, ser.encode({ name: "hΓ«Ll0!" })); + assert.deepEqual( + [...struct.attribValue("name", 0)], + [104, 195, 171, 76, 108, 48, 33, 0, 0, 0] + ); + assert.deepEqual(ser.decode(struct.index(0)), { name: "hΓ«Ll0!" }); + // overwrite w/ shorter string + struct.setIndex(0, ser.encode({ name: "πŸ€—" })); + assert.deepEqual(ser.decode(struct.index(0)), { name: "πŸ€—" }); + assert.doesNotThrow( + () => struct.setIndex(0, ser.encode({ name: "123456789" })), + "maxlen" + ); + assert.deepEqual( + [...struct.attribValue("name", 0)], + [49, 50, 51, 52, 53, 54, 55, 56, 57, 0] + ); + assert.throws( + () => struct.setIndex(0, ser.encode({ name: "1234567890" })), + "too large" + ); + }); +}); diff --git a/packages/soa/test/soa.ts b/packages/soa/test/soa.ts new file mode 100644 index 0000000000..4c82890ddc --- /dev/null +++ b/packages/soa/test/soa.ts @@ -0,0 +1,56 @@ +import { Type } from "@thi.ng/api"; +import { equiv } from "@thi.ng/equiv"; +import * as assert from "assert"; +import { soa } from "../src"; + +describe("soa", () => { + it("basic", () => { + const struct = soa(2, { + a: { type: Type.U16 }, + b: { size: 2, default: [1, 2] }, + c: { type: Type.I8, size: 2, default: [-3, 4] } + }); + assert.equal(struct.length, 2); + assert.deepEqual(struct.keys(), ["a", "b", "c"]); + assert(struct.buffers.a instanceof Uint16Array); + assert.equal(struct.buffers.a.length, 2); + assert(struct.buffers.b instanceof Float32Array); + assert.equal(struct.buffers.b.length, 4); + assert(struct.buffers.c instanceof Int8Array); + assert.equal(struct.buffers.c.length, 4); + assert( + equiv( + [...struct.values()], + [ + { a: [0], b: [1, 2], c: [-3, 4] }, + { a: [0], b: [1, 2], c: [-3, 4] } + ] + ) + ); + }); + + it("copy", () => { + const src = soa(2, { + a: { type: Type.U16 }, + b: { size: 2, default: [1, 2] }, + c: { type: Type.I8, size: 2, default: [-3, 4] } + }); + const dest = soa(4, { + a: { type: Type.U16, default: [0xaa55] }, + b: { size: 2 }, + c: { type: Type.I8, size: 2 } + }); + src.copyTo(dest, undefined, 2); + assert( + equiv( + [...dest.values()], + [ + { a: [0xaa55], b: [0, 0], c: [0, 0] }, + { a: [0xaa55], b: [0, 0], c: [0, 0] }, + { a: [0], b: [1, 2], c: [-3, 4] }, + { a: [0], b: [1, 2], c: [-3, 4] } + ] + ) + ); + }); +}); diff --git a/packages/soa/test/tsconfig.json b/packages/soa/test/tsconfig.json new file mode 100644 index 0000000000..4cd5c34dfc --- /dev/null +++ b/packages/soa/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build", + "module": "commonjs", + "noUnusedParameters": false, + "noUnusedLocals": false + }, + "include": ["./**/*.ts", "../src/**/*.ts"] +} diff --git a/packages/soa/tsconfig.json b/packages/soa/tsconfig.json new file mode 100644 index 0000000000..893b9979c5 --- /dev/null +++ b/packages/soa/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "module": "es6", + "target": "es6" + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/sparse/CHANGELOG.md b/packages/sparse/CHANGELOG.md index 397907a35f..ddbdce499a 100644 --- a/packages/sparse/CHANGELOG.md +++ b/packages/sparse/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/sparse@0.1.21...@thi.ng/sparse@0.1.22) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/sparse + + + + + +## [0.1.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/sparse@0.1.20...@thi.ng/sparse@0.1.21) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/sparse + + + + + +## [0.1.20](https://github.com/thi-ng/umbrella/compare/@thi.ng/sparse@0.1.19...@thi.ng/sparse@0.1.20) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/sparse + + + + + +## [0.1.19](https://github.com/thi-ng/umbrella/compare/@thi.ng/sparse@0.1.18...@thi.ng/sparse@0.1.19) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/sparse + + + + + ## [0.1.18](https://github.com/thi-ng/umbrella/compare/@thi.ng/sparse@0.1.17...@thi.ng/sparse@0.1.18) (2019-07-31) **Note:** Version bump only for package @thi.ng/sparse diff --git a/packages/sparse/package.json b/packages/sparse/package.json index 4199d8101b..0eec600af0 100644 --- a/packages/sparse/package.json +++ b/packages/sparse/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/sparse", - "version": "0.1.18", + "version": "0.1.22", "description": "Sparse vector & matrix implementations", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "adjacency", diff --git a/packages/strings/CHANGELOG.md b/packages/strings/CHANGELOG.md index b2ccaf8f13..9b2074a6ee 100644 --- a/packages/strings/CHANGELOG.md +++ b/packages/strings/CHANGELOG.md @@ -3,6 +3,33 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/strings@1.3.0...@thi.ng/strings@1.3.1) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/strings + + + + + +# [1.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/strings@1.2.3...@thi.ng/strings@1.3.0) (2019-09-21) + + +### Features + +* **strings:** add charRange(), add radix & zero-pad presets ([c9e5a63](https://github.com/thi-ng/umbrella/commit/c9e5a63)) + + + + + +## [1.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/strings@1.2.2...@thi.ng/strings@1.2.3) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/strings + + + + + ## [1.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/strings@1.2.1...@thi.ng/strings@1.2.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/strings diff --git a/packages/strings/package.json b/packages/strings/package.json index c679a6e9a1..9db90f59d3 100644 --- a/packages/strings/package.json +++ b/packages/strings/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/strings", - "version": "1.2.2", + "version": "1.3.1", "description": "Various string formatting & utility functions", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/errors": "^1.1.2", - "@thi.ng/memoize": "^1.1.2" + "@thi.ng/errors": "^1.2.1", + "@thi.ng/memoize": "^1.1.5" }, "keywords": [ "composition", diff --git a/packages/strings/src/index.ts b/packages/strings/src/index.ts index b4e8c7bbfa..cf2fef6ed3 100644 --- a/packages/strings/src/index.ts +++ b/packages/strings/src/index.ts @@ -9,6 +9,7 @@ export * from "./pad-right"; export * from "./parse"; export * from "./percent"; export * from "./radix"; +export * from "./range"; export * from "./repeat"; export * from "./slugify"; export * from "./splice"; diff --git a/packages/strings/src/pad-left.ts b/packages/strings/src/pad-left.ts index f3073f7daa..12b35fe938 100644 --- a/packages/strings/src/pad-left.ts +++ b/packages/strings/src/pad-left.ts @@ -20,3 +20,18 @@ export const padLeft: ( ? ((x = x.toString()), x.length < n ? buf.substr(x.length) + x : x) : buf; }); + +/** + * Zero-padded 2 digit formatter. + */ +export const Z2 = padLeft(2, "0"); + +/** + * Zero-padded 3 digit formatter. + */ +export const Z3 = padLeft(3, "0"); + +/** + * Zero-padded 4 digit formatter. + */ +export const Z4 = padLeft(4, "0"); diff --git a/packages/strings/src/radix.ts b/packages/strings/src/radix.ts index c7e7808a5f..6c4f06fa04 100644 --- a/packages/strings/src/radix.ts +++ b/packages/strings/src/radix.ts @@ -29,6 +29,16 @@ export const radix: ( */ export const B8 = radix(2, 8); +/** + * 16bit binary conversion preset. + */ +export const B16 = radix(2, 16); + +/** + * 32bit binary conversion preset. + */ +export const B32 = radix(2, 32); + /** * 8bit hex conversion preset. * Assumes unsigned inputs. diff --git a/packages/strings/src/range.ts b/packages/strings/src/range.ts new file mode 100644 index 0000000000..422e528c07 --- /dev/null +++ b/packages/strings/src/range.ts @@ -0,0 +1,20 @@ +/** + * Yields iterator of characters [`from`..`to`] (inclusive). Uses + * reverse ordering if `to` < `from`. + * + * @param from + * @param to + */ +export function* charRange(from: string | number, to: string | number) { + let i = typeof from === "string" ? from.charCodeAt(0) : from; + const end = typeof to === "string" ? to.charCodeAt(0) : to; + if (i <= end) { + for (; i <= end; i++) { + yield String.fromCharCode(i); + } + } else { + for (; i >= end; i--) { + yield String.fromCharCode(i); + } + } +} diff --git a/packages/transducers-binary/CHANGELOG.md b/packages/transducers-binary/CHANGELOG.md index 3532ce4592..f01a95b0d3 100644 --- a/packages/transducers-binary/CHANGELOG.md +++ b/packages/transducers-binary/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.4.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-binary@0.4.5...@thi.ng/transducers-binary@0.4.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/transducers-binary + + + + + +## [0.4.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-binary@0.4.4...@thi.ng/transducers-binary@0.4.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/transducers-binary + + + + + +## [0.4.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-binary@0.4.3...@thi.ng/transducers-binary@0.4.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/transducers-binary + + + + + +## [0.4.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-binary@0.4.2...@thi.ng/transducers-binary@0.4.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/transducers-binary + + + + + ## [0.4.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-binary@0.4.1...@thi.ng/transducers-binary@0.4.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/transducers-binary diff --git a/packages/transducers-binary/package.json b/packages/transducers-binary/package.json index 47f03bec49..8617425c96 100644 --- a/packages/transducers-binary/package.json +++ b/packages/transducers-binary/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers-binary", - "version": "0.4.2", + "version": "0.4.6", "description": "Binary data related transducers & reducers", "module": "./index.js", "main": "./lib/index.js", @@ -29,14 +29,14 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/compose": "^1.3.2", - "@thi.ng/random": "^1.1.10", - "@thi.ng/strings": "^1.2.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/compose": "^1.3.5", + "@thi.ng/random": "^1.1.13", + "@thi.ng/strings": "^1.3.1", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "base64", diff --git a/packages/transducers-binary/src/bytes.ts b/packages/transducers-binary/src/bytes.ts index 65c991498e..6fe9b2b614 100644 --- a/packages/transducers-binary/src/bytes.ts +++ b/packages/transducers-binary/src/bytes.ts @@ -65,11 +65,26 @@ export function bytes(cap = 1024, src?: Iterable) { return buf; }; + const setArray = ( + fn: string, + stride: number, + acc: Uint8Array, + x: any, + le: boolean + ) => { + const n = x.length; + acc = ensure(acc, stride * n); + for (let i = 0; i < n; i++, pos += stride) { + (view)[fn](pos, x[i], le); + } + return acc; + }; + return src ? reduce(bytes(cap), src) : >[ () => new Uint8Array(cap), - (acc) => acc.slice(0, pos), + (acc) => acc.subarray(0, pos), (acc, [type, x, le = false]) => { if (!view || view.buffer !== acc.buffer) { cap = acc.byteLength; @@ -108,79 +123,49 @@ export function bytes(cap = 1024, src?: Iterable) { view.setInt16(pos, x, le); pos += 2; break; - case Type.I16_ARRAY: { - x = >x; - const n = x.length; - acc = ensure(acc, 2 * n); - for (let i = 0; i < n; i++, pos += 2) - view.setInt16(pos, x[i], le); + case Type.I16_ARRAY: + acc = setArray("setInt16", 2, acc, x, le); break; - } case Type.U16: acc = ensure(acc, 4); view.setUint16(pos, x, le); pos += 4; break; - case Type.U16_ARRAY: { - x = >x; - const n = x.length; - acc = ensure(acc, 2 * n); - for (let i = 0; i < n; i++, pos += 2) - view.setUint16(pos, x[i], le); + case Type.U16_ARRAY: + acc = setArray("setUint16", 2, acc, x, le); break; - } case Type.I32: acc = ensure(acc, 4); view.setInt32(pos, x, le); pos += 4; break; - case Type.I32_ARRAY: { - x = >x; - const n = x.length; - acc = ensure(acc, 4 * n); - for (let i = 0; i < n; i++, pos += 4) - view.setInt32(pos, x[i], le); + case Type.I32_ARRAY: + acc = setArray("setInt32", 4, acc, x, le); break; - } case Type.U32: acc = ensure(acc, 4); view.setUint32(pos, x, le); pos += 4; break; - case Type.U32_ARRAY: { - x = >x; - const n = x.length; - acc = ensure(acc, 4 * n); - for (let i = 0; i < n; i++, pos += 4) - view.setUint32(pos, x[i], le); + case Type.U32_ARRAY: + acc = setArray("setUint32", 4, acc, x, le); break; - } case Type.F32: acc = ensure(acc, 4); view.setFloat32(pos, x, le); pos += 4; break; - case Type.F32_ARRAY: { - x = >x; - const n = x.length; - acc = ensure(acc, 4 * n); - for (let i = 0; i < n; i++, pos += 4) - view.setFloat32(pos, x[i], le); + case Type.F32_ARRAY: + acc = setArray("setFloat32", 4, acc, x, le); break; - } case Type.F64: acc = ensure(acc, 8); view.setFloat64(pos, x, le); pos += 8; break; - case Type.F64_ARRAY: { - x = >x; - const n = x.length; - acc = ensure(acc, 8 * n); - for (let i = 0; i < n; i++, pos += 8) - view.setFloat64(pos, x[i], le); + case Type.F64_ARRAY: + acc = setArray("setFloat64", 8, acc, x, le); break; - } case Type.STR: { let utf = [...utf8Encode(x)]; acc = ensure(acc, utf.length); diff --git a/packages/transducers-binary/src/utf8.ts b/packages/transducers-binary/src/utf8.ts index 87f7638272..3fb82d8b72 100644 --- a/packages/transducers-binary/src/utf8.ts +++ b/packages/transducers-binary/src/utf8.ts @@ -201,19 +201,18 @@ export const utf8Length = (str: string) => { if (u >= 0xd800 && u <= 0xdfff) { u = (0x10000 + ((u & 0x3ff) << 10)) | (str.charCodeAt(++i) & 0x3ff); } - if (u <= 0x7f) { - len++; - } else if (u <= 0x7ff) { - len += 2; - } else if (u <= 0xffff) { - len += 3; - } else if (u <= 0x1fffff) { - len += 4; - } else if (u <= 0x3ffffff) { - len += 5; - } else { - len += 6; - } + len += + u <= 0x7f + ? 1 + : u <= 0x7ff + ? 2 + : u <= 0xffff + ? 3 + : u <= 0x1fffff + ? 4 + : u <= 0x3ffffff + ? 5 + : 6; } return len; }; diff --git a/packages/transducers-fsm/CHANGELOG.md b/packages/transducers-fsm/CHANGELOG.md index f30a8e1247..25c2cd8cb5 100644 --- a/packages/transducers-fsm/CHANGELOG.md +++ b/packages/transducers-fsm/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-fsm@1.1.5...@thi.ng/transducers-fsm@1.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/transducers-fsm + + + + + +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-fsm@1.1.4...@thi.ng/transducers-fsm@1.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/transducers-fsm + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-fsm@1.1.3...@thi.ng/transducers-fsm@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/transducers-fsm + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-fsm@1.1.2...@thi.ng/transducers-fsm@1.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/transducers-fsm + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-fsm@1.1.1...@thi.ng/transducers-fsm@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/transducers-fsm diff --git a/packages/transducers-fsm/README.md b/packages/transducers-fsm/README.md index 4ef903b81e..f4a6d61eb6 100644 --- a/packages/transducers-fsm/README.md +++ b/packages/transducers-fsm/README.md @@ -50,7 +50,11 @@ state and once entered will cause processing to terminate (also see API description further below). ```ts -const testFSM = fsm.fsm({ +import { fsm } from '@thi.ng/transducers-fsm' +import * as tx from '@thi.ng/transducers' +import { isOdd } from '@thi.ng/checks' + +const testFSM = fsm({ // initial state initializer // (called before processing 1st input) @@ -107,7 +111,7 @@ const testFSM = fsm.fsm({ tx.mapcat((x) => x.split(/[,\s]+/g)), tx.map((x) => parseInt(x)), testFSM, - tx.filter(tx.odd), + tx.filter(isOdd) ), ["9,8,7,6", "14 1 0 17 15 16", "19,23,12,42,4"] ) diff --git a/packages/transducers-fsm/package.json b/packages/transducers-fsm/package.json index 4afa2fa4bb..bce83ac5ab 100644 --- a/packages/transducers-fsm/package.json +++ b/packages/transducers-fsm/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers-fsm", - "version": "1.1.2", + "version": "1.1.6", "description": "Transducer-based Finite State Machine transformer", "module": "./index.js", "main": "./lib/index.js", @@ -29,12 +29,12 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "ES6", diff --git a/packages/transducers-hdom/CHANGELOG.md b/packages/transducers-hdom/CHANGELOG.md index 73788a31d9..d01f54a714 100644 --- a/packages/transducers-hdom/CHANGELOG.md +++ b/packages/transducers-hdom/CHANGELOG.md @@ -3,6 +3,46 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.0.31](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-hdom@2.0.30...@thi.ng/transducers-hdom@2.0.31) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/transducers-hdom + + + + + +## [2.0.30](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-hdom@2.0.29...@thi.ng/transducers-hdom@2.0.30) (2019-09-23) + +**Note:** Version bump only for package @thi.ng/transducers-hdom + + + + + +## [2.0.29](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-hdom@2.0.28...@thi.ng/transducers-hdom@2.0.29) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/transducers-hdom + + + + + +## [2.0.28](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-hdom@2.0.27...@thi.ng/transducers-hdom@2.0.28) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/transducers-hdom + + + + + +## [2.0.27](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-hdom@2.0.26...@thi.ng/transducers-hdom@2.0.27) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/transducers-hdom + + + + + ## [2.0.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-hdom@2.0.25...@thi.ng/transducers-hdom@2.0.26) (2019-07-31) **Note:** Version bump only for package @thi.ng/transducers-hdom diff --git a/packages/transducers-hdom/package.json b/packages/transducers-hdom/package.json index 1f0a40c942..d29570329d 100644 --- a/packages/transducers-hdom/package.json +++ b/packages/transducers-hdom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers-hdom", - "version": "2.0.26", + "version": "2.0.31", "description": "Transducer based UI updater for @thi.ng/hdom", "module": "./index.js", "main": "./lib/index.js", @@ -29,13 +29,13 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/hdom": "^8.0.2", - "@thi.ng/hiccup": "^3.2.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/hdom": "^8.0.7", + "@thi.ng/hiccup": "^3.2.6", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "diff", diff --git a/packages/transducers-stats/CHANGELOG.md b/packages/transducers-stats/CHANGELOG.md index f6172b70b0..1833ff1def 100644 --- a/packages/transducers-stats/CHANGELOG.md +++ b/packages/transducers-stats/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-stats@1.1.5...@thi.ng/transducers-stats@1.1.6) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/transducers-stats + + + + + +## [1.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-stats@1.1.4...@thi.ng/transducers-stats@1.1.5) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/transducers-stats + + + + + +## [1.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-stats@1.1.3...@thi.ng/transducers-stats@1.1.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/transducers-stats + + + + + +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-stats@1.1.2...@thi.ng/transducers-stats@1.1.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/transducers-stats + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-stats@1.1.1...@thi.ng/transducers-stats@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/transducers-stats diff --git a/packages/transducers-stats/README.md b/packages/transducers-stats/README.md index c2428a49e7..0672b561a6 100644 --- a/packages/transducers-stats/README.md +++ b/packages/transducers-stats/README.md @@ -68,7 +68,7 @@ For some realworld use, please see the [crypto chart](https://github.com/thi-ng/umbrella/tree/master/examples/crypto-chart) example. -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/screenshots/crypto-chart.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/examples/crypto-chart.png) ```ts import * as tx from "@thi.ng/transducers"; diff --git a/packages/transducers-stats/package.json b/packages/transducers-stats/package.json index bcbb0ef4a2..c7086c7b1c 100644 --- a/packages/transducers-stats/package.json +++ b/packages/transducers-stats/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers-stats", - "version": "1.1.2", + "version": "1.1.6", "description": "Transducers for statistical / technical analysis", "module": "./index.js", "main": "./lib/index.js", @@ -29,14 +29,14 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/checks": "^2.2.2", - "@thi.ng/dcons": "^2.1.2", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/checks": "^2.4.1", + "@thi.ng/dcons": "^2.1.6", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "ES6", diff --git a/packages/transducers/CHANGELOG.md b/packages/transducers/CHANGELOG.md index 5610cd4741..9b2eb65a5d 100644 --- a/packages/transducers/CHANGELOG.md +++ b/packages/transducers/CHANGELOG.md @@ -3,6 +3,60 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [6.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@5.4.5...@thi.ng/transducers@6.0.0) (2019-11-09) + + +### Code Refactoring + +* **transducers:** rename old `interpolate` => `tween` ([918721d](https://github.com/thi-ng/umbrella/commit/918721dada9bab8045e397f13a2f77290eea2c88)) +* **transducers:** simplify args for extendSides, padSides, wrapSides ([a36651a](https://github.com/thi-ng/umbrella/commit/a36651a3aadb951a3d8bd117ddfa0dddf48d36ac)) +* **transducers:** update tween() args ([5523582](https://github.com/thi-ng/umbrella/commit/552358207cdf1dfdcb2ca78deac326ecad895fa9)) + + +### Features + +* **transducers:** add new iterators: extendSides/padSides/symmetric() ([47001fc](https://github.com/thi-ng/umbrella/commit/47001fc9c16f53af427e872b04929113d69463aa)) +* **transducers:** add new transducers: interpolate, interpolateHermite/Linear ([c3fa9ab](https://github.com/thi-ng/umbrella/commit/c3fa9ab90797af1d89e05f1c3821a87f9aa8a543)) + + +### BREAKING CHANGES + +* **transducers:** replace tween() args w/ `TweenOpts` config object +* **transducers:** rename `interpolate` iterator => `tween` +* **transducers:** Rename wrap() => wrapSides(), update signature to be +aligned w/ related iterators + + + + + +## [5.4.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@5.4.4...@thi.ng/transducers@5.4.5) (2019-09-21) + + +### Bug Fixes + +* **transducers:** fix mean() for reduce w/ init value ([d993bf2](https://github.com/thi-ng/umbrella/commit/d993bf2)) + + + + + +## [5.4.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@5.4.3...@thi.ng/transducers@5.4.4) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/transducers + + + + + +## [5.4.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@5.4.2...@thi.ng/transducers@5.4.3) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/transducers + + + + + ## [5.4.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@5.4.1...@thi.ng/transducers@5.4.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/transducers diff --git a/packages/transducers/README.md b/packages/transducers/README.md index 0a6e44c74f..6059e661ac 100644 --- a/packages/transducers/README.md +++ b/packages/transducers/README.md @@ -11,12 +11,14 @@ This project is part of the - [About](#about) - [Tutorial](#tutorial) + - [6.0.0 release](#600-release) - [5.0.0 release](#500-release) - [Related packages](#related-packages) - [Installation](#installation) - [Dependencies](#dependencies) - [Usage examples](#usage-examples) - [Basic usage patterns](#basic-usage-patterns) + - [Interpolation & SVG generation](#interpolation--svg-generation) - [Fuzzy search](#fuzzy-search) - [Histogram generation & result grouping](#histogram-generation--result-grouping) - [Pagination](#pagination) @@ -72,6 +74,25 @@ patterns of this package, specifically these 3 parts: - [Part 3 - Convolution, 1D/2D Cellular automata](https://medium.com/@thi.ng/of-umbrellas-transducers-reactive-streams-mushrooms-pt-3-a1c4e621db9b) - [Part 4 - Disjoint Sets, Graph analysis, Signed Distance Fields](https://medium.com/@thi.ng/of-umbrellas-transducers-reactive-streams-mushrooms-pt-4-62d8e71e5603) +### 6.0.0 release + +BREAKING CHANGES: + +- The `interpolate` iterator for keyframe interpolation has been renamed + to `tween`. In its place there's a new higher order transducer called + `interpolate`, incl. syntax-sugar versions `interpolateHermite` and + `interpolateLinear`. +- The previously deprecated `wrapLeft`, `wrapRight` and `wrapBoth` + iterators have been removed. +- The `wrap` iterator has been renamed to `wrapSides` and has a new + signature/arguments, more aligned with the ones listed below. + +The following new iterators have been added: + +- `extendSides` +- `padSides` +- `symmetric` + ### 5.0.0 release Several previously included internal support functions have been @@ -79,7 +100,7 @@ migrated to the [@thi.ng/arrays](https://github.com/thi-ng/umbrella/tree/master/packages/arrays) package. You'll need to update your imports if you've been using any of these directly. Note that some of these functions also have changes to -their arg order. +their arg order. See changelog. Functions using randomness now all support an optional PRNG implementation of the `IRandom` interface from the @@ -94,6 +115,7 @@ package. - [@thi.ng/transducers-fsm](https://github.com/thi-ng/umbrella/tree/master/packages/transducers-fsm) - Fine State Machine transducer - [@thi.ng/transducers-hdom](https://github.com/thi-ng/umbrella/tree/master/packages/transducers-hdom) - Transducer based [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/master/packages/hdom) UI updates - [@thi.ng/transducers-stats](https://github.com/thi-ng/umbrella/tree/master/packages/transducers-stats) - Technical / statistical analysis transducers +- [@thi.ng/grid-iterators](https://github.com/thi-ng/umbrella/tree/master/packages/grid-iterators) - 2D grid coordinate iteration strategies #### Packages utilizing transducers @@ -113,11 +135,14 @@ yarn add @thi.ng/transducers ## Dependencies - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) +- [@thi.ng/arrays](https://github.com/thi-ng/umbrella/tree/master/packages/arrays) - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/master/packages/checks) - [@thi.ng/compare](https://github.com/thi-ng/umbrella/tree/master/packages/compare) - [@thi.ng/compose](https://github.com/thi-ng/umbrella/tree/master/packages/compose) - [@thi.ng/equiv](https://github.com/thi-ng/umbrella/tree/master/packages/equiv) - [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/master/packages/errors) +- [@thi.ng/math](https://github.com/thi-ng/umbrella/tree/master/packages/math) +- [@thi.ng/random](https://github.com/thi-ng/umbrella/tree/master/packages/random) - [@thi.ng/strings](https://github.com/thi-ng/umbrella/tree/master/packages/strings) ## Usage examples @@ -177,6 +202,44 @@ f(4) // undefined f = tx.step(take) ``` +### Interpolation & SVG generation + +This example uses the +[@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/master/packages/geom) +package for quick SVG generation. + +![example output](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/transducers/hermite-tx.png) + +```ts +import { asSvg, svgDoc, circle, polyline } from "@thi.ng/geom"; + +// source values +const values = [5, 10, 4, 8, 20, 2, 11, 7]; + +// interpolate values and transform into 2D points +const vertices = [...tx.iterator( + tx.comp( + tx.interpolateHermite(10), + tx.mapIndexed((x, y) => [x, y]) + ), + // duplicate first & last vals (1x LHS / 2x RHS) + // this is only needed for hermite interpolation + // (see doc string for `interpolateHermite`) + tx.extendSides(values, 1, 2) +)]; + +// generate SVG +asSvg( + svgDoc( + { width: 800, height: 200, "stroke-width": 0.1 }, + // interpolated points as polyline + polyline(vertices, { stroke: "red" }), + // original values as dots + ...values.map((y, x) => circle([x * 10, y], 0.2)) + ) +) +``` + ### Fuzzy search ```ts @@ -677,6 +740,9 @@ tx.transduce(tx.map((x) => x*10), tx.push(), tx.range(4)) - [flatten](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/xform/flatten.ts) - [indexed](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/xform/indexed.ts) - [interleave](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/xform/interleave.ts) +- [interpolate](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/xform/interpolate.ts) +- [interpolate-hermite](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/xform/interpolate-hermite.ts) +- [interpolate-linear](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/xform/interpolate-linear.ts) - [interpose](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/xform/interpose.ts) - [keep](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/xform/keep.ts) - [labeled](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/xform/labeled.ts) @@ -727,10 +793,11 @@ tx.transduce(tx.map((x) => x*10), tx.push(), tx.range(4)) - [choices](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/choices.ts) - [concat](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/concat.ts) - [cycle](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/cycle.ts) -- [interpolate](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/interpolate.ts) +- [extendSides](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/extend-sides.ts) - [iterate](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/iterate.ts) - [keys](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/keys.ts) - [normRange](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/normRange.ts) +- [padSides](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/pad-sides.ts) - [pairs](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/pairs.ts) - [permutations](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/permutations.ts) - [permutationsN](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/permutationsN.ts) @@ -740,12 +807,10 @@ tx.transduce(tx.map((x) => x*10), tx.push(), tx.range(4)) - [repeat](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/repeat.ts) - [repeatedly](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/repeatedly.ts) - [reverse](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/reverse.ts) -- [tuples](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/zip.ts) (deprecated, use `zip`) +- [symmetric](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/symmetric.ts) +- [tween](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/tween.ts) - [vals](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/vals.ts) -- [wrapBoth](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/wrapBoth.ts) -- [wrapLeft](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/wrapLeft.ts) -- [wrapRight](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/wrapRight.ts) -- [wrap](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/wrap.ts) +- [wrapSides](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/wrap-sides.ts) - [zip](https://github.com/thi-ng/umbrella/tree/master/packages/transducers/src/iter/zip.ts) ### Reducers diff --git a/packages/transducers/package.json b/packages/transducers/package.json index cf1bd74730..3aeeef7e4b 100644 --- a/packages/transducers/package.json +++ b/packages/transducers/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers", - "version": "5.4.2", + "version": "6.0.0", "description": "Lightweight transducer implementations for ES6 / TypeScript", "module": "./index.js", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "build:test": "rimraf build && tsc -p test/tsconfig.json", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", - "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib func iter rfn xform", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib func internal iter rfn xform", "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", "pub": "yarn build:release && yarn publish --access public" }, @@ -29,19 +29,20 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/arrays": "^0.2.2", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/compare": "^1.0.9", - "@thi.ng/compose": "^1.3.2", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/random": "^1.1.10", - "@thi.ng/strings": "^1.2.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/arrays": "^0.3.0", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/compare": "^1.0.10", + "@thi.ng/compose": "^1.3.5", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/math": "^1.5.0", + "@thi.ng/random": "^1.1.13", + "@thi.ng/strings": "^1.3.1" }, "keywords": [ "array", diff --git a/packages/transducers/src/index.ts b/packages/transducers/src/index.ts index e6bcadf85b..898541b4bc 100644 --- a/packages/transducers/src/index.ts +++ b/packages/transducers/src/index.ts @@ -49,6 +49,9 @@ export * from "./xform/flatten-with"; export * from "./xform/flatten"; export * from "./xform/indexed"; export * from "./xform/interleave"; +export * from "./xform/interpolate"; +export * from "./xform/interpolate-hermite"; +export * from "./xform/interpolate-linear"; export * from "./xform/interpose"; export * from "./xform/keep"; export * from "./xform/labeled"; @@ -106,10 +109,11 @@ export * from "./iter/as-iterable"; export * from "./iter/choices"; export * from "./iter/concat"; export * from "./iter/cycle"; -export * from "./iter/interpolate"; +export * from "./iter/extend-sides"; export * from "./iter/iterate"; export * from "./iter/keys"; export * from "./iter/norm-range"; +export * from "./iter/pad-sides"; export * from "./iter/pairs"; export * from "./iter/permutations"; export * from "./iter/range"; @@ -118,6 +122,8 @@ export * from "./iter/range3d"; export * from "./iter/repeat"; export * from "./iter/repeatedly"; export * from "./iter/reverse"; +export * from "./iter/symmetric"; +export * from "./iter/tween"; export * from "./iter/vals"; -export * from "./iter/wrap"; +export * from "./iter/wrap-sides"; export * from "./iter/zip"; diff --git a/packages/transducers/src/internal/drain.ts b/packages/transducers/src/internal/drain.ts new file mode 100644 index 0000000000..eeaca8b398 --- /dev/null +++ b/packages/transducers/src/internal/drain.ts @@ -0,0 +1,22 @@ +import { Fn } from "@thi.ng/api"; +import { ReductionFn } from "../api"; +import { isReduced } from "../reduced"; + +/** + * Helper HOF yielding a buffer drain completion function for some + * transducers. + * + * @param buf + * @param complete + * @param reduce + */ +export const __drain = ( + buf: T[], + complete: Fn, + reduce: ReductionFn +) => (acc: T[]) => { + while (buf.length && !isReduced(acc)) { + acc = reduce(acc, buf.shift()!); + } + return complete(acc); +}; diff --git a/packages/transducers/src/internal/group-opts.ts b/packages/transducers/src/internal/group-opts.ts new file mode 100644 index 0000000000..7c10ef7ed0 --- /dev/null +++ b/packages/transducers/src/internal/group-opts.ts @@ -0,0 +1,17 @@ +import { identity } from "@thi.ng/compose"; +import { GroupByOpts } from "../api"; +import { push } from "../rfn/push"; + +/** + * Shared helper function for groupBy* reducers + * + * @param opts + */ +export const __groupByOpts = ( + opts?: Partial> +) => + >{ + key: identity, + group: push(), + ...opts + }; diff --git a/packages/transducers/src/internal/mathop.ts b/packages/transducers/src/internal/mathop.ts new file mode 100644 index 0000000000..0a80f2cf80 --- /dev/null +++ b/packages/transducers/src/internal/mathop.ts @@ -0,0 +1,17 @@ +import { FnAny } from "@thi.ng/api"; +import { Reducer, ReductionFn } from "../api"; +import { $$reduce, reducer } from "../reduce"; + +export const __mathop = ( + rfn: FnAny>, + fn: ReductionFn, + initDefault: number, + args: any[] +) => { + const res = $$reduce(rfn, args); + if (res !== undefined) { + return res; + } + const init = args[0] || initDefault; + return reducer(() => init, fn); +}; diff --git a/packages/transducers/src/internal/sort-opts.ts b/packages/transducers/src/internal/sort-opts.ts new file mode 100644 index 0000000000..604b0cdc84 --- /dev/null +++ b/packages/transducers/src/internal/sort-opts.ts @@ -0,0 +1,10 @@ +import { compare } from "@thi.ng/compare"; +import { identity } from "@thi.ng/compose"; +import { SortOpts } from "../api"; + +export const __sortOpts = (opts?: Partial>) => + >{ + key: identity, + compare, + ...opts + }; diff --git a/packages/transducers/src/iter/extend-sides.ts b/packages/transducers/src/iter/extend-sides.ts new file mode 100644 index 0000000000..54c61dda44 --- /dev/null +++ b/packages/transducers/src/iter/extend-sides.ts @@ -0,0 +1,45 @@ +import { SEMAPHORE } from "@thi.ng/api"; +import { repeat } from "./repeat"; + +/** + * Yields iterator of given iterable which repeats the first and/or last + * value(s) `numLeft`/`numRight` times (default: 1). By default both + * sides are repeated, but can be adjusted by setting either of them to + * zero. `numRight` defaults to same value as `numLeft`. + * + * ``` + * [...extendSides([1, 2, 3])] + * // [ 1, 1, 2, 3, 3] + * + * [...extendSides([1, 2, 3], 3)] + * // [ 1, 1, 1, 1, 2, 3, 3, 3, 3 ] + * + * [...extendSides([1, 2, 3], 0, 3)] + * // [ 1, 2, 3, 3, 3, 3 ] + * ``` + * + * @see padSides + * @see wrapSides + * + * @param src + * @param numLeft + * @param numRight + */ +export function* extendSides( + src: Iterable, + numLeft = 1, + numRight = numLeft +) { + let prev: T | typeof SEMAPHORE = SEMAPHORE; + for (let x of src) { + if (numLeft > 0 && prev === SEMAPHORE) { + yield* repeat(x, numLeft); + numLeft = 0; + } + yield x; + prev = x; + } + if (numRight > 0 && prev !== SEMAPHORE) { + yield* repeat(prev, numRight); + } +} diff --git a/packages/transducers/src/iter/interpolate.ts b/packages/transducers/src/iter/interpolate.ts deleted file mode 100644 index a567d80646..0000000000 --- a/packages/transducers/src/iter/interpolate.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Fn2 } from "@thi.ng/api"; -import { normRange } from "./norm-range"; -import { repeat } from "./repeat"; - -/** - * Takes a number of keyframe tuples (`stops`) and yields a sequence of - * `n+1` equally spaced, interpolated values. Keyframes are defined as - * `[pos, value]`. Only values in the closed `minPos` .. `maxPos` - * interval will be computed. - * - * Interpolation happens in two stages: First the given `init` function - * is called to transform/prepare pairs of consecutive keyframes into a - * single interval (user defined). Then to produce each interpolated - * value calls `mix` with the currently active interval and - * interpolation time value `t` (re-normalized and relative to current - * interval). The iterator yields results of these `mix()` function - * calls. - * - * Depending on the overall number of samples requested and the distance - * between keyframes, some keyframes MIGHT be skipped. E.g. if - * requesting 10 samples within [0,1], the interval between two - * successive keyframes at 0.12 and 0.19 would be skipped entirely, - * since samples will only be taken at multiples of `1/n` (0.0, 0.1, - * 0.2... in this example). - * - * The given keyframe positions can lie outside the `minPos`/`maxPos` - * range and also don't need to cover the range fully. In the latter - * case, interpolated values before the first or after the last keyframe - * will yield the value of the 1st/last keyframe. If only a single - * keyframe is given in total, all `n` yielded samples will be that - * keyframe's transformed value. - * - * ``` - * [...interpolate( - * 10, - * 0, - * 100, - * (a, b) => [a, b], - * ([a, b], t) => Math.floor(a + (b - a) * t), - * [20, 100], - * [50, 200], - * [80, 0] - * )] - * // [ 100, 100, 100, 133, 166, 200, 133, 66, 0, 0, 0 ] - * ``` - * - * Using easing functions (e.g. from thi.ng/math), non-linear - * interpolation within each keyframe interval can be achieved: - * - * ``` - * import { mix, smoothStep } from "@thi.ng/math" - * - * [...interpolate( - * 10, - * 0, - * 100, - * (a, b) => [a, b], - * ([a, b], t) => Math.floor(mix(a, b, smoothStep(0.1, 0.9, t))), - * [20, 100], - * [50, 200], - * [80, 0] - * )] - * // [ 100, 100, 100, 120, 179, 200, 158, 41, 0, 0, 0 ] - * ``` - * - * @param n - * @param minPos - * @param maxPos - * @param init interval producer (from 2 keyframe values) - * @param mix interval interpolator - * @param stops keyframe / stops - */ -export function* interpolate( - n: number, - minPos: number, - maxPos: number, - init: Fn2, - mix: Fn2, - ...stops: [number, A][] -): IterableIterator { - let l = stops.length; - if (l < 1) return; - if (l === 1) { - yield* repeat(mix(init(stops[0][1], stops[0][1]), 0), n); - } - stops.sort((a, b) => a[0] - b[0]); - if (stops[l - 1][0] < maxPos) { - stops.push([maxPos, stops[l - 1][1]]); - } - if (stops[0][0] > minPos) { - stops.unshift([minPos, stops[0][1]]); - } - const range = maxPos - minPos; - let start = stops[0][0]; - let end = stops[1][0]; - let delta = end - start; - let interval = init(stops[0][1], stops[1][1]); - let i = 1; - l = stops.length; - for (let t of normRange(n)) { - t = minPos + range * t; - if (t > end) { - while (i < l && t > stops[i][0]) i++; - start = stops[i - 1][0]; - end = stops[i][0]; - delta = end - start; - interval = init(stops[i - 1][1], stops[i][1]); - } - yield mix(interval, delta !== 0 ? (t - start) / delta : 0); - } -} diff --git a/packages/transducers/src/iter/pad-sides.ts b/packages/transducers/src/iter/pad-sides.ts new file mode 100644 index 0000000000..100dbcf31d --- /dev/null +++ b/packages/transducers/src/iter/pad-sides.ts @@ -0,0 +1,43 @@ +import { concat } from "./concat"; +import { repeat } from "./repeat"; + +/** + * Returns iterator of `src` padded with value `x`, repeated + * `numLeft`/`numRight` times (default: 1). By default both sides are + * padded, but can be adjusted by setting either of them to zero. + * `numRight` defaults to same value as `numLeft`. + * + * Essentially, syntax sugar for: + * + * ``` + * // default + * concat(repeat(x, numLeft), src, repeat(x, numRight)) + * + * // left only + * concat(repeat(x, numLeft), src) + * + * // right only + * concat(src, repeat(x, numRight)) + * ``` + * + * @see extendsSides + * @see wrapSides + * + * @param src + * @param x + * @param numLeft + * @param numRight + */ +export const padSides = ( + src: Iterable, + x: T, + numLeft = 1, + numRight = numLeft +) => + numLeft > 0 + ? numRight > 0 + ? concat(repeat(x, numLeft), src, repeat(x, numRight)) + : concat(repeat(x, numLeft), src) + : numRight > 0 + ? concat(src, repeat(x, numRight)) + : concat(src); diff --git a/packages/transducers/src/iter/permutations.ts b/packages/transducers/src/iter/permutations.ts index 0b12c9be0d..818c7f6f26 100644 --- a/packages/transducers/src/iter/permutations.ts +++ b/packages/transducers/src/iter/permutations.ts @@ -38,7 +38,7 @@ export function permutations( d: Iterable ): IterableIterator<[A, B, C, D]>; export function permutations(...src: Iterable[]): IterableIterator; -export function* permutations(...src: any[]) { +export function* permutations(...src: any[]): IterableIterator { const n = src.length - 1; if (n < 0) { return; diff --git a/packages/transducers/src/iter/range2d.ts b/packages/transducers/src/iter/range2d.ts index 1d98555080..699f7a860b 100644 --- a/packages/transducers/src/iter/range2d.ts +++ b/packages/transducers/src/iter/range2d.ts @@ -19,7 +19,9 @@ export function range2d( stepX: number, stepY: number ): IterableIterator<[number, number]>; -export function* range2d(...args: number[]) { +export function* range2d( + ...args: number[] +): IterableIterator<[number, number]> { let fromX!: number, toX!: number, stepX!: number; let fromY!: number, toY!: number, stepY!: number; switch (args.length) { diff --git a/packages/transducers/src/iter/range3d.ts b/packages/transducers/src/iter/range3d.ts index 52beeca337..27f2d08a3f 100644 --- a/packages/transducers/src/iter/range3d.ts +++ b/packages/transducers/src/iter/range3d.ts @@ -25,7 +25,9 @@ export function range3d( stepY: number, stepZ: number ): IterableIterator<[number, number, number]>; -export function* range3d(...args: number[]) { +export function* range3d( + ...args: number[] +): IterableIterator<[number, number, number]> { let fromX!: number, toX!: number, stepX!: number; let fromY!: number, toY!: number, stepY!: number; let fromZ!: number, toZ!: number, stepZ!: number; diff --git a/packages/transducers/src/iter/symmetric.ts b/packages/transducers/src/iter/symmetric.ts new file mode 100644 index 0000000000..95e8d29b1d --- /dev/null +++ b/packages/transducers/src/iter/symmetric.ts @@ -0,0 +1,28 @@ +interface Cell { + x: T; + n?: Cell; +} + +/** + * Yields an iterator of all `src` values, followed by the same values + * in reverse order. Efficiently builds the reversed order via an + * internal linked list. + * + * ``` + * [...symmetric([1, 2, 3])] + * // [ 1, 2, 3, 3, 2, 1 ] + * ``` + * + * @param src + */ +export function* symmetric(src: Iterable) { + let head: Cell | undefined = undefined; + for (let x of src) { + head = { x, n: head }; + yield x; + } + while (head) { + yield head.x; + head = head.n; + } +} diff --git a/packages/transducers/src/iter/tween.ts b/packages/transducers/src/iter/tween.ts new file mode 100644 index 0000000000..ef82a09d0f --- /dev/null +++ b/packages/transducers/src/iter/tween.ts @@ -0,0 +1,124 @@ +import { Fn2 } from "@thi.ng/api"; +import { normRange } from "./norm-range"; +import { repeat } from "./repeat"; + +export interface TweenOpts { + /** + * Total number (n+1) of tweened values to produce + */ + num: number; + /** + * Min time boundary. Only values in the closed `[min..max]` + * time interval will be computed. + */ + min: number; + /** + * Max time boundary. Only values in the closed `[min..max]` + * time interval will be computed. + */ + max: number; + /** + * Interval producer (from 2 keyframe values, i.e. `stops`) + */ + init: Fn2; + /** + * Interval interpolator + */ + mix: Fn2; + /** + * Keyframe definitions, i.e. `[time, value]` tuples + */ + stops: [number, A][]; +} + +/** + * Keyframe based interpolator. Yields a sequence of `num+1` equally + * spaced, tweened values from given keyframe tuples (`stops`). + * Keyframes are defined as `[time, value]` tuples. Only values in the + * closed `[min..max]` time interval will be computed. + * + * Interpolation happens in two stages: First the given `init` function + * is called to transform/prepare pairs of consecutive keyframes into a + * single interval (user defined). Then, to produce each tweened value + * calls `mix` with the currently active interval and interpolation time + * value `t` (re-normalized and relative to current interval). The + * iterator yields results of these `mix()` function calls. + * + * Depending on the overall `num`ber of samples requested and the + * distance between keyframes, some keyframes MIGHT be skipped. E.g. if + * requesting 10 samples within [0,1], the interval between two + * successive keyframes at 0.12 and 0.19 would be skipped entirely, + * since samples will only be taken at multiples of `1/num` (i.e. 0.0, + * 0.1, 0.2... in this example). + * + * The given keyframe times can lie outside the `min`/`max` range and + * also don't need to cover the range fully. In the latter case, tweened + * values before the first or after the last keyframe will yield the + * value of the first/last keyframe. If only a single keyframe is given + * in total, all `num` yielded samples will be that keyframe's + * transformed value. + * + * ``` + * [...tween({ + * num: 10, + * min: 0, + * max: 100, + * init: (a, b) => [a, b], + * mix: ([a, b], t) => Math.floor(a + (b - a) * t), + * stops: [[20, 100], [50, 200], [80, 0]] + * })] + * // [ 100, 100, 100, 133, 166, 200, 133, 66, 0, 0, 0 ] + * ``` + * + * Using easing functions (e.g. from thi.ng/math), non-linear + * interpolation within each keyframe interval can be achieved: + * + * ``` + * import { mix, smoothStep } from "@thi.ng/math" + * + * [...tween({ + * num: 10, + * min: 0, + * max: 100, + * init: (a, b) => [a, b], + * mix: ([a, b], t) => Math.floor(mix(a, b, smoothStep(0.1, 0.9, t))), + * stops: [[20, 100], [50, 200], [80, 0]] + * })] + * // [ 100, 100, 100, 120, 179, 200, 158, 41, 0, 0, 0 ] + * ``` + * @see TweenOpts + * @see interpolate + * @see interpolateHermite + * @see interpolateLinear + * + * @param opts + */ +export function* tween(opts: TweenOpts): IterableIterator { + const { min, max, num, init, mix, stops } = opts; + let l = stops.length; + if (l < 1) return; + if (l === 1) { + yield* repeat(mix(init(stops[0][1], stops[0][1]), 0), num); + } + stops.sort((a, b) => a[0] - b[0]); + stops[l - 1][0] < max && stops.push([max, stops[l - 1][1]]); + stops[0][0] > min && stops.unshift([min, stops[0][1]]); + const range = max - min; + let start = stops[0][0]; + let end = stops[1][0]; + let delta = end - start; + let interval = init(stops[0][1], stops[1][1]); + let i = 1; + l = stops.length; + for (let t of normRange(num)) { + t = min + range * t; + if (t > end) { + while (i < l && t > stops[i][0]) i++; + start = stops[i - 1][0]; + end = stops[i][0]; + delta = end - start; + interval = init(stops[i - 1][1], stops[i][1]); + } + yield mix(interval, delta !== 0 ? (t - start) / delta : 0); + } +} diff --git a/packages/transducers/src/iter/wrap-both.ts b/packages/transducers/src/iter/wrap-both.ts deleted file mode 100644 index fab96f56f3..0000000000 --- a/packages/transducers/src/iter/wrap-both.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { wrap } from "./wrap"; - -/** - * See `wrap()`. - * - * @deprecated superceded by `wrap()` - * @param src - * @param n - */ -export const wrapBoth = (src: Iterable, n = 1) => wrap(src, n); diff --git a/packages/transducers/src/iter/wrap-left.ts b/packages/transducers/src/iter/wrap-left.ts deleted file mode 100644 index 675ffab365..0000000000 --- a/packages/transducers/src/iter/wrap-left.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { wrap } from "./wrap"; - -/** - * See `wrap()`. - * - * @deprecated superceded by `wrap()` - * @param src - * @param n - */ -export const wrapLeft = (src: Iterable, n = 1) => - wrap(src, n, true, false); diff --git a/packages/transducers/src/iter/wrap-right.ts b/packages/transducers/src/iter/wrap-right.ts deleted file mode 100644 index 2e5033b3b7..0000000000 --- a/packages/transducers/src/iter/wrap-right.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { wrap } from "./wrap"; - -/** - * See `wrap()`. - * - * @deprecated superceded by `wrap()` - * @param src - * @param n - */ -export const wrapRight = (src: Iterable, n = 1) => - wrap(src, n, false, true); diff --git a/packages/transducers/src/iter/wrap-sides.ts b/packages/transducers/src/iter/wrap-sides.ts new file mode 100644 index 0000000000..108df6011d --- /dev/null +++ b/packages/transducers/src/iter/wrap-sides.ts @@ -0,0 +1,38 @@ +import { ensureArray } from "@thi.ng/arrays"; +import { illegalArgs } from "@thi.ng/errors"; +import { inRange } from "@thi.ng/math"; + +/** + * Yields iterator of `src` with the last `numLeft` values of `src` + * prepended at the beginning and/or the first `numRight` values + * appended at the end. `numLeft` defaults to 1 and `numRight` defaults + * to same value as `numLeft`, therefore wraps both sides by default and + * throws error if either `nXXX` < 0 or larger than `src.length`. + * + * @see extendSides + * @see padSides + * + * @param src + * @param numLeft + * @param numRight + */ +export function* wrapSides( + src: Iterable, + numLeft = 1, + numRight = numLeft +) { + const _src: T[] = ensureArray(src); + !(inRange(numLeft, 0, _src.length) && inRange(numRight, 0, _src.length)) && + illegalArgs(`allowed wrap range: [0..${_src.length}]`); + if (numLeft > 0) { + for (let m = _src.length, i = m - numLeft; i < m; i++) { + yield _src[i]; + } + } + yield* _src; + if (numRight > 0) { + for (let i = 0; i < numRight; i++) { + yield _src[i]; + } + } +} diff --git a/packages/transducers/src/iter/wrap.ts b/packages/transducers/src/iter/wrap.ts deleted file mode 100644 index b1af8f8e99..0000000000 --- a/packages/transducers/src/iter/wrap.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ensureArray } from "@thi.ng/arrays"; -import { illegalArgs } from "@thi.ng/errors"; - -/** - * Yields iterator of `src` with the last `n` values of `src` prepended - * at the beginning (if `left` is truthy) and/or the first `n` values - * appended at the end (if `right` is truthy). Wraps both sides by - * default and throws error if `n` < 0 or larger than `src.length`. - * - * @param src - * @param n - * @param left - * @param right - */ -export function* wrap(src: Iterable, n = 1, left = true, right = true) { - const _src: T[] = ensureArray(src); - (n < 0 || n > _src.length) && - illegalArgs( - `wrong number of wrap items: got ${n}, but max: ${_src.length}` - ); - if (left) { - for (let m = _src.length, i = m - n; i < m; i++) { - yield _src[i]; - } - } - yield* _src; - if (right) { - for (let i = 0; i < n; i++) { - yield _src[i]; - } - } -} diff --git a/packages/transducers/src/reduce.ts b/packages/transducers/src/reduce.ts index c9a608d44c..81005e4b25 100644 --- a/packages/transducers/src/reduce.ts +++ b/packages/transducers/src/reduce.ts @@ -4,53 +4,66 @@ import { illegalArity } from "@thi.ng/errors"; import { IReducible, Reducer, ReductionFn } from "./api"; import { isReduced, unreduced } from "./reduced"; +const parseArgs = (args: any[]) => + args.length === 2 + ? [undefined, args[1]] + : args.length === 3 + ? [args[1], args[2]] + : illegalArity(args.length); + export function reduce(rfn: Reducer, xs: Iterable): A; export function reduce(rfn: Reducer, acc: A, xs: Iterable): A; export function reduce(rfn: Reducer, xs: IReducible): A; -export function reduce( - rfn: Reducer, - acc: A, - xs: IReducible -): A; +// prettier-ignore +export function reduce(rfn: Reducer, acc: A, xs: IReducible): A; export function reduce(...args: any[]): A { - let acc!: A, xs!: Iterable | IReducible; - switch (args.length) { - case 3: - xs = args[2]; - acc = args[1]; - break; - case 2: - xs = args[1]; - break; - default: - illegalArity(args.length); - } const rfn = args[0]; const init = rfn[0]; const complete = rfn[1]; const reduce = rfn[2]; - acc = acc == null ? init() : acc; - if (implementsFunction(xs, "$reduce")) { - acc = (>xs).$reduce(reduce, acc); - } else if (isArrayLike(xs)) { - for (let i = 0, n = xs.length; i < n; i++) { - acc = reduce(acc, xs[i]); - if (isReduced(acc)) { - acc = (acc).deref(); - break; - } + args = parseArgs(args); + const acc: A = args[0] == null ? init() : args[0]; + const xs: Iterable | IReducible = args[1]; + return unreduced( + complete( + implementsFunction(xs, "$reduce") + ? (>xs).$reduce(reduce, acc) + : isArrayLike(xs) + ? reduceArray(reduce, acc, xs) + : reduceIterable(reduce, acc, >xs) + ) + ); +} + +const reduceArray = ( + rfn: ReductionFn, + acc: A, + xs: ArrayLike +) => { + for (let i = 0, n = xs.length; i < n; i++) { + acc = rfn(acc, xs[i]); + if (isReduced(acc)) { + acc = (acc).deref(); + break; } - } else { - for (let x of >xs) { - acc = reduce(acc, x); - if (isReduced(acc)) { - acc = (acc).deref(); - break; - } + } + return acc; +}; + +const reduceIterable = ( + rfn: ReductionFn, + acc: A, + xs: Iterable +) => { + for (let x of xs) { + acc = rfn(acc, x); + if (isReduced(acc)) { + acc = (acc).deref(); + break; } } - return unreduced(complete(acc)); -} + return acc; +}; /** * Convenience helper for building a full `Reducer` using the identity diff --git a/packages/transducers/src/rfn/add.ts b/packages/transducers/src/rfn/add.ts index 17aa67c9ea..b86a7b443c 100644 --- a/packages/transducers/src/rfn/add.ts +++ b/packages/transducers/src/rfn/add.ts @@ -1,17 +1,12 @@ import { Reducer } from "../api"; -import { $$reduce, reducer } from "../reduce"; +import { __mathop } from "../internal/mathop"; /** - * Reducer to compute sum of values with given `init` value. + * Reducer to compute sum of values with given `init` value. Default: 0 */ export function add(init?: number): Reducer; export function add(xs: Iterable): number; export function add(init: number, xs: Iterable): number; export function add(...args: any[]): any { - const res = $$reduce(add, args); - if (res !== undefined) { - return res; - } - const init = args[0] || 0; - return reducer(() => init, (acc, x: number) => acc + x); + return __mathop(add, (acc, x: number) => acc + x, 0, args); } diff --git a/packages/transducers/src/rfn/group-by-map.ts b/packages/transducers/src/rfn/group-by-map.ts index b60be89279..accb00e8d0 100644 --- a/packages/transducers/src/rfn/group-by-map.ts +++ b/packages/transducers/src/rfn/group-by-map.ts @@ -1,28 +1,20 @@ -import { identity } from "@thi.ng/compose"; import { GroupByOpts, Reducer } from "../api"; +import { __groupByOpts } from "../internal/group-opts"; import { $$reduce, reducer } from "../reduce"; -import { push } from "./push"; -export function groupByMap( - opts?: Partial> -): Reducer, SRC>; +// prettier-ignore +export function groupByMap(opts?: Partial>): Reducer, SRC>; export function groupByMap(xs: Iterable): Map; -export function groupByMap( - opts: Partial>, - xs: Iterable -): Map; +// prettier-ignore +export function groupByMap(opts: Partial>, xs: Iterable): Map; export function groupByMap(...args: any[]): any { const res = $$reduce(groupByMap, args); if (res !== undefined) { return res; } - const opts = >{ - key: identity, - group: push(), - ...args[0] - }; + const opts = __groupByOpts(args[0]); const [init, _, reduce] = opts.group; - _; + _; // ignore return reducer, SRC>( () => new Map(), (acc, x) => { diff --git a/packages/transducers/src/rfn/group-by-obj.ts b/packages/transducers/src/rfn/group-by-obj.ts index 883406f445..3b50a29942 100644 --- a/packages/transducers/src/rfn/group-by-obj.ts +++ b/packages/transducers/src/rfn/group-by-obj.ts @@ -1,33 +1,25 @@ import { IObjectOf } from "@thi.ng/api"; -import { identity } from "@thi.ng/compose"; import { GroupByOpts, Reducer } from "../api"; +import { __groupByOpts } from "../internal/group-opts"; import { $$reduce, reducer } from "../reduce"; -import { push } from "./push"; -export function groupByObj( - opts?: Partial> -): Reducer, SRC>; +// prettier-ignore +export function groupByObj(opts?: Partial>): Reducer, SRC>; export function groupByObj(xs: Iterable): IObjectOf; -export function groupByObj( - opts: Partial>, - xs: Iterable -): IObjectOf; +// prettier-ignore +export function groupByObj(opts: Partial>, xs: Iterable): IObjectOf; export function groupByObj(...args: any[]): any { const res = $$reduce(groupByObj, args); if (res) { return res; } - const _opts = >{ - key: identity, - group: push(), - ...args[0] - }; - const [_init, _, _reduce] = _opts.group; - _; + const opts = __groupByOpts(args[0]); + const [_init, _, _reduce] = opts.group; + _; // ignore return reducer, SRC>( () => ({}), (acc, x: SRC) => { - const k: any = _opts.key(x); + const k: any = opts.key(x); acc[k] = acc[k] ? _reduce(acc[k], x) : _reduce(_init(), x); diff --git a/packages/transducers/src/rfn/mean.ts b/packages/transducers/src/rfn/mean.ts index ac8e58ee14..7022083d84 100644 --- a/packages/transducers/src/rfn/mean.ts +++ b/packages/transducers/src/rfn/mean.ts @@ -8,11 +8,11 @@ import { reduce } from "../reduce"; export function mean(): Reducer; export function mean(xs: Iterable): number; export function mean(xs?: Iterable): any { - let n = 0; + let n = 1; return xs ? reduce(mean(), xs) : >[ - () => 0, + () => (n = 0), (acc) => (n > 1 ? acc / n : acc), (acc, x) => (n++, acc + x) ]; diff --git a/packages/transducers/src/rfn/mul.ts b/packages/transducers/src/rfn/mul.ts index f6c98442ef..26c19aa3c7 100644 --- a/packages/transducers/src/rfn/mul.ts +++ b/packages/transducers/src/rfn/mul.ts @@ -1,5 +1,5 @@ import { Reducer } from "../api"; -import { $$reduce, reducer } from "../reduce"; +import { __mathop } from "../internal/mathop"; /** * Reducer to compute product of values with optional `init` value @@ -9,10 +9,5 @@ export function mul(init?: number): Reducer; export function mul(xs: Iterable): number; export function mul(init: number, xs: Iterable): number; export function mul(...args: any[]): any { - const res = $$reduce(mul, args); - if (res !== undefined) { - return res; - } - const init = args[0] || 1; - return reducer(() => init, (acc, x: number) => acc * x); + return __mathop(mul, (acc, x: number) => acc * x, 1, args); } diff --git a/packages/transducers/src/rfn/sub.ts b/packages/transducers/src/rfn/sub.ts index 80ff9349ef..769ef4cb33 100644 --- a/packages/transducers/src/rfn/sub.ts +++ b/packages/transducers/src/rfn/sub.ts @@ -1,5 +1,5 @@ import { Reducer } from "../api"; -import { $$reduce, reducer } from "../reduce"; +import { __mathop } from "../internal/mathop"; /** * Reducer to successively subtract values from optional `init` value @@ -9,10 +9,5 @@ export function sub(init?: number): Reducer; export function sub(xs: Iterable): number; export function sub(init: number, xs: Iterable): number; export function sub(...args: any[]): any { - const res = $$reduce(sub, args); - if (res !== undefined) { - return res; - } - const init = args[0] || 0; - return reducer(() => init, (acc, x: number) => acc - x); + return __mathop(sub, (acc, x: number) => acc - x, 0, args); } diff --git a/packages/transducers/src/xform/convolve.ts b/packages/transducers/src/xform/convolve.ts index 8568e72cc9..48e3a785c5 100644 --- a/packages/transducers/src/xform/convolve.ts +++ b/packages/transducers/src/xform/convolve.ts @@ -85,12 +85,12 @@ const kernelLookup1d = ( border: number ): Fn<[number, number], number> => wrap - ? ([w, ox]) => { + ? ({ 0: w, 1: ox }) => { const xx = x < -ox ? width + ox : x >= width - ox ? ox - 1 : x + ox; return w * src[xx]; } - : ([w, ox]) => { + : ({ 0: w, 1: ox }) => { return x < -ox || x >= width - ox ? border : w * src[x + ox]; }; @@ -104,14 +104,14 @@ const kernelLookup2d = ( border: number ): Fn<[number, [number, number]], number> => wrap - ? ([w, [ox, oy]]) => { + ? ({ 0: w, 1: { 0: ox, 1: oy } }) => { const xx = x < -ox ? width + ox : x >= width - ox ? ox - 1 : x + ox; const yy = y < -oy ? height + oy : y >= height - oy ? oy - 1 : y + oy; return w * src[yy * width + xx]; } - : ([w, [ox, oy]]) => { + : ({ 0: w, 1: { 0: ox, 1: oy } }) => { return x < -ox || y < -oy || x >= width - ox || y >= height - oy ? border : w * src[(y + oy) * width + x + ox]; diff --git a/packages/transducers/src/xform/interpolate-hermite.ts b/packages/transducers/src/xform/interpolate-hermite.ts new file mode 100644 index 0000000000..67b814f40e --- /dev/null +++ b/packages/transducers/src/xform/interpolate-hermite.ts @@ -0,0 +1,33 @@ +import { mixHermite } from "@thi.ng/math"; +import { Transducer } from "../api"; +import { interpolate } from "./interpolate"; + +/** + * Pre-configured version of `interpolate()` for numeric values and + * using cubic hermite interpolation. The number of samples per interval + * is configurable. No values will be produced if there're less than 4 + * inputs. + * + * Note: Due to the nature of hermite interpolation, the very first and + * last input are only used to compute the curve tangents, but will not + * appear in the output. Use the `extendSides()` iterator to transform + * the input so that these values are duplicated and so are used as part + * of an interpolation interval. + * + * @see interpolate + * @see interpolateLinear + * @see extendSides + * + * @param n + */ +export function interpolateHermite(n: number): Transducer; +// prettier-ignore +export function interpolateHermite(n: number, src: Iterable): IterableIterator; +export function interpolateHermite(n: number, src?: Iterable): any { + return interpolate( + (chunk, t) => (mixHermite)(...chunk, t), + 4, + n, + src! + ); +} diff --git a/packages/transducers/src/xform/interpolate-linear.ts b/packages/transducers/src/xform/interpolate-linear.ts new file mode 100644 index 0000000000..8d8463658b --- /dev/null +++ b/packages/transducers/src/xform/interpolate-linear.ts @@ -0,0 +1,26 @@ +import { mix } from "@thi.ng/math"; +import { Transducer } from "../api"; +import { interpolate } from "./interpolate"; + +/** + * Pre-configured version of `interpolate()` for numeric values and + * using pairwise linear interpolation. The number of samples per + * interval is configurable. No values will be produced if there're less + * than 2 inputs. + * + * @see interpolate + * @see interpolateHermit + * + * @param n + */ +export function interpolateLinear(n: number): Transducer; +// prettier-ignore +export function interpolateLinear(n: number, src: Iterable): IterableIterator; +export function interpolateLinear(n: number, src?: Iterable): any { + return interpolate( + (chunk, t) => (mix)(...chunk, t), + 2, + n, + src! + ); +} diff --git a/packages/transducers/src/xform/interpolate.ts b/packages/transducers/src/xform/interpolate.ts new file mode 100644 index 0000000000..a375af3dfd --- /dev/null +++ b/packages/transducers/src/xform/interpolate.ts @@ -0,0 +1,56 @@ +import { Fn2 } from "@thi.ng/api"; +import { Transducer } from "../api"; +import { comp } from "../func/comp"; +import { normRange } from "../iter/norm-range"; +import { iterator } from "../iterator"; +import { map } from "./map"; +import { mapcat } from "./mapcat"; +import { partition } from "./partition"; + +/** + * Higher order interpolation transducer. The resulting transducer forms + * a sliding window and calls `fn` (the given interpolation function) + * `n` times with the current window and a normalized time value to + * produce the requested number of interpolated values per interval. If + * the optional `src` iterable is given, `interpolate` returns an + * iterator of interpolated values. No values will be produced if the + * number of inputs is less than given `window` size. + * + * Note: The *very last* input value can never be fully reached and + * might need to be explicitly duplicated in the input, e.g. via the + * `extendSides()` iterator... + * + * ``` + * [...interpolate( + * ([a, b], t) => a + (b - a) * t, + * 2, + * 8, + * [0, 1, 0, 2] + * )] + * + * // [ 0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, + * // 1, 0.875, 0.75, 0.625, 0.5, 0.375, 0.25, 0.125, + * // 0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75 ] + * ``` + * + * @see interpolateHermite + * @see interpolateLinear + * @see extendSides + * + * @param fn + * @param window + * @param n + */ +// prettier-ignore +export function interpolate(fn: Fn2, window: number, n: number): Transducer; +// prettier-ignore +export function interpolate(fn: Fn2, window: number, n: number, src: Iterable): IterableIterator; +// prettier-ignore +export function interpolate(fn: Fn2, window: number, n: number, src?: Iterable) { + return src + ? iterator(interpolate(fn, window, n), src) + : comp( + partition(window, 1), + mapcat((chunk) => map((t) => fn(chunk, t), normRange(n, false))) + ); +} diff --git a/packages/transducers/src/xform/moving-average.ts b/packages/transducers/src/xform/moving-average.ts index d9dc216068..5d44ad7988 100644 --- a/packages/transducers/src/xform/moving-average.ts +++ b/packages/transducers/src/xform/moving-average.ts @@ -18,10 +18,8 @@ import { iterator1 } from "../iterator"; * @param src */ export function movingAverage(period: number): Transducer; -export function movingAverage( - period: number, - src: Iterable -): IterableIterator; +// prettier-ignore +export function movingAverage(period: number, src: Iterable): IterableIterator; export function movingAverage(period: number, src?: Iterable): any { return src ? iterator1(movingAverage(period), src) diff --git a/packages/transducers/src/xform/moving-median.ts b/packages/transducers/src/xform/moving-median.ts index 1c86b70c6c..2a55a38652 100644 --- a/packages/transducers/src/xform/moving-median.ts +++ b/packages/transducers/src/xform/moving-median.ts @@ -1,7 +1,6 @@ -import { compare as cmp } from "@thi.ng/compare"; -import { identity } from "@thi.ng/compose"; import { SortOpts, Transducer } from "../api"; import { comp } from "../func/comp"; +import { __sortOpts } from "../internal/sort-opts"; import { $iter } from "../iterator"; import { map } from "./map"; import { partition } from "./partition"; @@ -16,29 +15,18 @@ import { partition } from "./partition"; * @param opts * @param src */ -export function movingMedian( - n: number, - opts?: Partial> -): Transducer; -export function movingMedian( - n: number, - src: Iterable -): IterableIterator; -export function movingMedian( - n: number, - opts: Partial>, - src: Iterable -): IterableIterator; +// prettier-ignore +export function movingMedian(n: number, opts?: Partial>): Transducer; +// prettier-ignore +export function movingMedian(n: number, src: Iterable): IterableIterator; +// prettier-ignore +export function movingMedian(n: number, opts: Partial>, src: Iterable): IterableIterator; export function movingMedian(...args: any[]): any { const iter = $iter(movingMedian, args); if (iter) { return iter; } - const { key, compare } = >{ - key: identity, - compare: cmp, - ...args[1] - }; + const { key, compare } = __sortOpts(args[1]); const n = args[0]; const m = n >> 1; return comp( diff --git a/packages/transducers/src/xform/partition-sort.ts b/packages/transducers/src/xform/partition-sort.ts index 12af29d595..ab5c499b0b 100644 --- a/packages/transducers/src/xform/partition-sort.ts +++ b/packages/transducers/src/xform/partition-sort.ts @@ -1,7 +1,6 @@ -import { compare as cmp } from "@thi.ng/compare"; -import { identity } from "@thi.ng/compose"; import { SortOpts, Transducer } from "../api"; import { comp } from "../func/comp"; +import { __sortOpts } from "../internal/sort-opts"; import { $iter, iterator } from "../iterator"; import { mapcat } from "./mapcat"; import { partition } from "./partition"; @@ -32,29 +31,18 @@ import { partition } from "./partition"; * @param key sort key lookup * @param cmp comparator */ -export function partitionSort( - n: number, - opts?: Partial> -): Transducer; -export function partitionSort( - n: number, - src: Iterable -): IterableIterator; -export function partitionSort( - n: number, - opts: Partial>, - src: Iterable -): IterableIterator; +// prettier-ignore +export function partitionSort(n: number, opts?: Partial>): Transducer; +// prettier-ignore +export function partitionSort(n: number, src: Iterable): IterableIterator; +// prettier-ignore +export function partitionSort(n: number, opts: Partial>, src: Iterable): IterableIterator; export function partitionSort(...args: any[]): any { const iter = $iter(partitionSort, args, iterator); if (iter) { return iter; } - const { key, compare } = >{ - key: identity, - compare: cmp, - ...args[1] - }; + const { key, compare } = __sortOpts(args[1]); return comp( partition(args[0], true), mapcat((window: A[]) => diff --git a/packages/transducers/src/xform/stream-sort.ts b/packages/transducers/src/xform/stream-sort.ts index e9822ee722..42d0152b5f 100644 --- a/packages/transducers/src/xform/stream-sort.ts +++ b/packages/transducers/src/xform/stream-sort.ts @@ -1,9 +1,8 @@ import { binarySearch } from "@thi.ng/arrays"; -import { compare as cmp } from "@thi.ng/compare"; -import { identity } from "@thi.ng/compose"; import { Reducer, SortOpts, Transducer } from "../api"; +import { __drain } from "../internal/drain"; +import { __sortOpts } from "../internal/sort-opts"; import { $iter, iterator } from "../iterator"; -import { isReduced } from "../reduced"; /** * Transducer. Similar to `partitionSort()`, however uses proper sliding @@ -19,40 +18,24 @@ import { isReduced } from "../reduced"; * @param key * @param cmp */ -export function streamSort( - n: number, - opts?: Partial> -): Transducer; -export function streamSort( - n: number, - src: Iterable -): IterableIterator; -export function streamSort( - n: number, - opts: Partial>, - src: Iterable -): IterableIterator; +// prettier-ignore +export function streamSort(n: number, opts?: Partial>): Transducer; +// prettier-ignore +export function streamSort(n: number, src: Iterable): IterableIterator; +// prettier-ignore +export function streamSort(n: number, opts: Partial>, src: Iterable): IterableIterator; export function streamSort(...args: any[]): any { const iter = $iter(streamSort, args, iterator); if (iter) { return iter; } - const { key, compare } = >{ - key: identity, - compare: cmp, - ...args[1] - }; + const { key, compare } = __sortOpts(args[1]); const n = args[0]; return ([init, complete, reduce]: Reducer) => { const buf: A[] = []; return >[ init, - (acc) => { - while (buf.length && !isReduced(acc)) { - acc = reduce(acc, buf.shift()!); - } - return complete(acc); - }, + __drain(buf, complete, reduce), (acc, x) => { const idx = binarySearch(buf, x, key, compare); buf.splice(idx < 0 ? -(idx + 1) : idx, 0, x); diff --git a/packages/transducers/src/xform/take-last.ts b/packages/transducers/src/xform/take-last.ts index c4e0f7f026..b8da2e80fb 100644 --- a/packages/transducers/src/xform/take-last.ts +++ b/packages/transducers/src/xform/take-last.ts @@ -1,6 +1,6 @@ import { Reducer, Transducer } from "../api"; +import { __drain } from "../internal/drain"; import { iterator } from "../iterator"; -import { isReduced } from "../reduced"; /** * Transducer which only yields the last `n` values. Assumes @@ -23,12 +23,7 @@ export function takeLast(n: number, src?: Iterable): any { const buf: T[] = []; return >[ init, - (acc) => { - while (buf.length && !isReduced(acc)) { - acc = reduce(acc, buf.shift()!); - } - return complete(acc); - }, + __drain(buf, complete, reduce), (acc, x) => { if (buf.length === n) { buf.shift(); diff --git a/packages/unionstruct/CHANGELOG.md b/packages/unionstruct/CHANGELOG.md index f4a1ed6675..25d29b8d63 100644 --- a/packages/unionstruct/CHANGELOG.md +++ b/packages/unionstruct/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/unionstruct@1.1.2...@thi.ng/unionstruct@1.1.3) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/unionstruct + + + + + ## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/unionstruct@1.1.1...@thi.ng/unionstruct@1.1.2) (2019-07-31) **Note:** Version bump only for package @thi.ng/unionstruct diff --git a/packages/unionstruct/package.json b/packages/unionstruct/package.json index 65d2a0e581..b242acb673 100644 --- a/packages/unionstruct/package.json +++ b/packages/unionstruct/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/unionstruct", - "version": "1.1.2", + "version": "1.1.3", "description": "C-style struct, union and bitfield views of ArrayBuffers", "module": "./index.js", "main": "./lib/index.js", @@ -29,8 +29,8 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "keywords": [ "align", diff --git a/packages/vector-pools/CHANGELOG.md b/packages/vector-pools/CHANGELOG.md index 08349fb222..acecabb511 100644 --- a/packages/vector-pools/CHANGELOG.md +++ b/packages/vector-pools/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/vector-pools@1.0.8...@thi.ng/vector-pools@1.0.9) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/vector-pools + + + + + +## [1.0.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/vector-pools@1.0.7...@thi.ng/vector-pools@1.0.8) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/vector-pools + + + + + +## [1.0.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/vector-pools@1.0.6...@thi.ng/vector-pools@1.0.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/vector-pools + + + + + +## [1.0.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/vector-pools@1.0.5...@thi.ng/vector-pools@1.0.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/vector-pools + + + + + +## [1.0.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/vector-pools@1.0.4...@thi.ng/vector-pools@1.0.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/vector-pools + + + + + +## [1.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/vector-pools@1.0.3...@thi.ng/vector-pools@1.0.4) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/vector-pools + + + + + ## [1.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/vector-pools@1.0.2...@thi.ng/vector-pools@1.0.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/vector-pools diff --git a/packages/vector-pools/README.md b/packages/vector-pools/README.md index 9535a6bcee..58e758d649 100644 --- a/packages/vector-pools/README.md +++ b/packages/vector-pools/README.md @@ -10,6 +10,7 @@ This project is part of the - [About](#about) + - [Status](#status) - [Installation](#installation) - [Dependencies](#dependencies) - [Usage examples](#usage-examples) @@ -32,6 +33,13 @@ region of a WebGL or WASM memory buffer. * The only copying taking place is to GPU memory +### Status + +This package might be merged with and/or superseded by +[@thi.ng/ecs](https://github.com/thi-ng/umbrella/tree/master/packages/ecs) +/ +[@thi.ng/soa](https://github.com/thi-ng/umbrella/tree/master/packages/soa). + ## Installation ```bash diff --git a/packages/vector-pools/package.json b/packages/vector-pools/package.json index 67d5fd9967..497910c8b6 100644 --- a/packages/vector-pools/package.json +++ b/packages/vector-pools/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/vector-pools", - "version": "1.0.3", + "version": "1.0.9", "description": "Data structures for managing & working with strided, memory mapped vectors", "module": "./index.js", "main": "./lib/index.js", @@ -29,16 +29,16 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/binary": "^1.1.0", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/malloc": "^4.0.2", - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/api": "^6.5.0", + "@thi.ng/binary": "^1.1.1", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/malloc": "^4.1.0", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "ES6", diff --git a/packages/vector-pools/src/api.ts b/packages/vector-pools/src/api.ts index d6a0b7dccd..657ecc0345 100644 --- a/packages/vector-pools/src/api.ts +++ b/packages/vector-pools/src/api.ts @@ -1,6 +1,9 @@ import { + GLType, + ILogger, IObjectOf, IRelease, + NULL_LOGGER, Type, TypedArray } from "@thi.ng/api"; @@ -53,48 +56,6 @@ export type VecFactory = ( stride: number ) => StridedVec; -/** - * WebGL numeric type constants. These can be used by classes in this - * package as aliases for thi.ng/malloc's `Type` enum (see `GL2TYPE` LUT - * below), but also then used directly when initializing WebGL buffers - * from given attribute buffer specs. - * - * See `AttribPool` & readme examples for more details. - */ -export const enum GLType { - I8 = 0x1400, - U8 = 0x1401, - I16 = 0x1402, - U16 = 0x1403, - I32 = 0x1404, - U32 = 0x1405, - F32 = 0x1406 -} - -/** - * Conversion from `GLType` to `Type`. - */ -export const GL2TYPE: { [id: number]: Type } = { - [GLType.I8]: Type.I8, - [GLType.U8]: Type.U8, - [GLType.I16]: Type.I16, - [GLType.U16]: Type.U16, - [GLType.I32]: Type.I32, - [GLType.I32]: Type.I32, - [GLType.U32]: Type.U32, - [GLType.F32]: Type.F32 -}; +export let LOGGER = NULL_LOGGER; -/** - * Conversion from `Type` to `GLType`. - */ -export const TYPE2GL: { [id: number]: GLType } = { - [Type.I8]: GLType.I8, - [Type.U8]: GLType.U8, - [Type.I16]: GLType.I16, - [Type.U16]: GLType.U16, - [Type.I32]: GLType.I32, - [Type.I32]: GLType.I32, - [Type.U32]: GLType.U32, - [Type.F32]: GLType.F32 -}; +export const setLogger = (logger: ILogger) => (LOGGER = logger); diff --git a/packages/vector-pools/src/attrib-pool.ts b/packages/vector-pools/src/attrib-pool.ts index f4186c2d48..aed13e411d 100644 --- a/packages/vector-pools/src/attrib-pool.ts +++ b/packages/vector-pools/src/attrib-pool.ts @@ -3,13 +3,16 @@ import { IObjectOf, IRelease, SIZEOF, - TypedArray + TypedArray, + typedArray, + TYPEDARRAY_CTORS } from "@thi.ng/api"; import { align, Pow2 } from "@thi.ng/binary"; -import { MemPool, TYPEDARRAY_CTORS, wrap } from "@thi.ng/malloc"; +import { isNumber } from "@thi.ng/checks"; +import { MemPool } from "@thi.ng/malloc"; import { range } from "@thi.ng/transducers"; import { ReadonlyVec, Vec, zeroes } from "@thi.ng/vectors"; -import { AttribPoolOpts, AttribSpec } from "./api"; +import { AttribPoolOpts, AttribSpec, LOGGER } from "./api"; import { asNativeType } from "./convert"; /* @@ -90,7 +93,7 @@ export class AttribPool implements IRelease { attribValue(id: string, i: number): number | Vec | undefined { const spec = this.specs[id]; - assert(!!spec, `invalid attrib: ${id}`); + ensureSpec(spec, id); if (i >= this.capacity) return; i *= spec.stride!; return spec.size > 1 @@ -100,7 +103,7 @@ export class AttribPool implements IRelease { *attribValues(id: string) { const spec = this.specs[id]; - assert(!!spec, `invalid attrib: ${id}`); + ensureSpec(spec, id); const buf = this.attribs[id]; const stride = spec.stride!; const size = spec.size; @@ -117,12 +120,12 @@ export class AttribPool implements IRelease { attribArray(id: string) { const spec = this.specs[id]; - assert(!!spec, `invalid attrib: ${id}`); + ensureSpec(spec, id); const n = this.capacity; const size = spec.size; const stride = spec.stride!; const src = this.attribs[id]; - const dest = new TYPEDARRAY_CTORS[(asNativeType(spec.type))](n * size); + const dest = new TYPEDARRAY_CTORS[asNativeType(spec.type)](n * size); if (size > 1) { for (let i = 0, j = 0; i < n; i++, j += stride) { dest.set(src.subarray(j, j + size), i * size); @@ -137,22 +140,13 @@ export class AttribPool implements IRelease { setAttribValue(id: string, index: number, v: number | ReadonlyVec) { const spec = this.specs[id]; - assert(!!spec, `invalid attrib: ${id}`); this.ensure(index + 1); + const isNum = isNumber(v); + ensureAttrib(spec, id, isNum); const buf = this.attribs[id]; index *= spec.stride!; - const isNum = typeof v === "number"; - assert( - () => (!isNum && spec.size > 1) || (isNum && spec.size === 1), - `incompatible value for attrib: ${id}` - ); if (!isNum) { - assert( - (v).length <= spec.size, - `wrong attrib val size, expected ${spec.size}, got ${ - (v).length - }` - ); + ensureValueSize(v, spec.size); buf.set(v, index); } else { buf[index] = v; @@ -161,25 +155,16 @@ export class AttribPool implements IRelease { } setAttribValues(id: string, vals: ReadonlyVec | ReadonlyVec[], index = 0) { + const v = vals[0]; const spec = this.specs[id]; - assert(!!spec, `invalid attrib: ${id}`); + const isNum = isNumber(v); + ensureAttrib(spec, id, isNum); const n = vals.length; this.ensure(index + n); const stride = spec.stride!; const buf = this.attribs[id]; - const v = vals[0]; - const isNum = typeof v === "number"; - assert( - (!isNum && spec.size > 1) || (isNum && spec.size === 1), - `incompatible value(s) for attrib: ${id}` - ); if (!isNum) { - assert( - (v).length <= spec.size, - `wrong attrib val size, expected ${spec.size}, got ${ - (v).length - }` - ); + ensureValueSize(v, spec.size); for (let i = 0, j = index * stride; i < n; i++, j += stride) { buf.set(vals[i], j); } @@ -221,7 +206,7 @@ export class AttribPool implements IRelease { assert(newAddr > 0, `out of memory`); for (let id in this.specs) { const a = this.specs[id]; - const buf = wrap( + const buf = typedArray( asNativeType(a.type), this.pool.buf, newAddr + (a.byteOffset || 0), @@ -259,14 +244,12 @@ export class AttribPool implements IRelease { assert(a.size > 0, `attrib ${id}: illegal or missing size`); const size = SIZEOF[asNativeType(a.type)]; a.default == null && (a.default = a.size > 1 ? zeroes(a.size) : 0); - const isNum = typeof a.default === "number"; + const isNum = isNumber(a.default); assert( () => (!isNum && a.size === (a.default).length) || (isNum && a.size === 1), - `attrib ${id}: incompatible default value, expected size ${ - a.size - }` + `attrib ${id}: incompatible default value, expected size ${a.size}` ); assert( a.byteOffset % size === 0, @@ -291,7 +274,7 @@ export class AttribPool implements IRelease { ) { for (let id in specs) { const a = specs[id]; - this.attribs[id] = wrap( + this.attribs[id] = typedArray( asNativeType(a.type), this.pool.buf, this.addr + (a.byteOffset || 0), @@ -312,9 +295,9 @@ export class AttribPool implements IRelease { const buf = this.attribs[id]; const s = a.stride!; const v = a.default; - if (typeof v === "number") { + if (a.size === 1) { for (let i = start; i < end; i++) { - buf[i * s] = v; + buf[i * s] = v; } } else { for (let i = start; i < end; i++) { @@ -327,11 +310,11 @@ export class AttribPool implements IRelease { protected realign(newByteStride: number) { if (this.order.length === 0 || newByteStride === this.byteStride) return; - console.warn(`realigning ${this.byteStride} -> ${newByteStride}...`); + LOGGER.info(`realigning ${this.byteStride} -> ${newByteStride}...`); const grow = newByteStride > this.byteStride; let newAddr = this.addr; if (grow) { - assert(this.resizable, `pool resizing disabled`); + assert(this.resizable, `pool growth disabled`); newAddr = this.pool.realloc( this.addr, this.capacity * newByteStride @@ -343,47 +326,23 @@ export class AttribPool implements IRelease { const sameBlock = newAddr === this.addr; const num = this.capacity - 1; const attribs = this.attribs; - const newAttribs: IObjectOf<[TypedArray, number]> = {}; const specs = this.specs; const order = grow ? [...this.order].reverse() : this.order; // create resized attrib views (in old or new address space) - for (let id in specs) { - const a = specs[id]; - const type = asNativeType(a.type); - const dStride = newByteStride / SIZEOF[type]; - newAttribs[id] = [ - wrap( - type, - this.pool.buf, - newAddr + a.byteOffset, - num * dStride + a.size - ), - dStride - ]; - } - // process in opposite directions based on new stride size + const newAttribs = resizeAttribs( + specs, + this.pool.buf, + newAddr, + newByteStride, + num + ); + // process in opposite directions based on new stride size and + // in offset order to avoid successor attrib vals getting + // overwritten... for (let i of newByteStride < this.byteStride ? range(num + 1) : range(num, -1, -1)) { - // ...in offset order to avoid successor attrib vals - for (let id of order) { - const a = specs[id]; - const sStride = a.stride!; - const src = attribs[id]; - const [dest, dStride] = newAttribs[id]; - if (typeof a.default === "number") { - dest[i * dStride] = src[i * sStride]; - } else { - const j = i * sStride; - sameBlock - ? (grow ? dest : src).copyWithin( - i * dStride, - j, - j + a.size - ) - : dest.set(src.subarray(j, j + a.size), i * dStride); - } - } + moveAttribs(order, specs, attribs, newAttribs, i, sameBlock, grow); } this.addr = newAddr; this.byteStride = newByteStride; @@ -394,3 +353,66 @@ export class AttribPool implements IRelease { } } } + +const resizeAttribs = ( + specs: IObjectOf, + buf: ArrayBuffer, + dest: number, + stride: number, + num: number +) => { + const newAttribs: IObjectOf<[TypedArray, number]> = {}; + for (let id in specs) { + const a = specs[id]; + const type = asNativeType(a.type); + const dStride = stride / SIZEOF[type]; + newAttribs[id] = [ + typedArray(type, buf, dest + a.byteOffset, num * dStride + a.size), + dStride + ]; + } + return newAttribs; +}; + +const moveAttribs = ( + order: string[], + specs: IObjectOf, + attribs: IObjectOf, + newAttribs: IObjectOf<[TypedArray, number]>, + i: number, + sameBlock: boolean, + grow: boolean +) => { + for (let id of order) { + const a = specs[id]; + const sStride = a.stride!; + const src = attribs[id]; + const [dest, dStride] = newAttribs[id]; + if (a.size === 1) { + dest[i * dStride] = src[i * sStride]; + } else { + const saddr = i * sStride; + const daddr = i * dStride; + sameBlock + ? (grow ? dest : src).copyWithin(daddr, saddr, saddr + a.size) + : dest.set(src.subarray(saddr, saddr + a.size), daddr); + } + } +}; + +const ensureSpec = (spec: AttribSpec, id: string) => + assert(!!spec, `invalid attrib: ${id}`); + +const ensureAttrib = (spec: AttribSpec, id: string, isNum: boolean) => { + ensureSpec(spec, id); + assert( + () => (!isNum && spec.size > 1) || (isNum && spec.size === 1), + `incompatible value for attrib: ${id}` + ); +}; + +const ensureValueSize = (v: ReadonlyVec, size: number) => + assert( + v.length <= size, + `wrong attrib val size, expected ${size}, got ${v.length}` + ); diff --git a/packages/vector-pools/src/convert.ts b/packages/vector-pools/src/convert.ts index 7b03f0863c..4445c9a2ee 100644 --- a/packages/vector-pools/src/convert.ts +++ b/packages/vector-pools/src/convert.ts @@ -1,5 +1,9 @@ -import { Type } from "@thi.ng/api"; -import { GL2TYPE, GLType, TYPE2GL } from "./api"; +import { + GL2TYPE, + GLType, + Type, + TYPE2GL +} from "@thi.ng/api"; /** * Returns canonical `Type` value of `type` by first attempting to @@ -13,11 +17,11 @@ import { GL2TYPE, GLType, TYPE2GL } from "./api"; * @param type */ export const asNativeType = (type: GLType | Type): Type => { - const t = GL2TYPE[type]; + const t = (GL2TYPE)[type]; return t !== undefined ? t : type; }; export const asGLType = (type: GLType | Type): GLType => { - const t = TYPE2GL[type]; + const t = (TYPE2GL)[type]; return t !== undefined ? t : type; }; diff --git a/packages/vector-pools/src/vec-pool.ts b/packages/vector-pools/src/vec-pool.ts index 22ffce9c1b..baf47b4575 100644 --- a/packages/vector-pools/src/vec-pool.ts +++ b/packages/vector-pools/src/vec-pool.ts @@ -1,8 +1,8 @@ -import { Type, TypedArray } from "@thi.ng/api"; +import { GLType, Type, TypedArray } from "@thi.ng/api"; import { isTypedArray } from "@thi.ng/checks"; import { MemPool, MemPoolOpts, MemPoolStats } from "@thi.ng/malloc"; import { StridedVec } from "@thi.ng/vectors"; -import { GLType, IVecPool } from "./api"; +import { IVecPool } from "./api"; import { asNativeType } from "./convert"; import { wrap } from "./wrap"; diff --git a/packages/vectors/CHANGELOG.md b/packages/vectors/CHANGELOG.md index 648c8deb2b..f5298e681e 100644 --- a/packages/vectors/CHANGELOG.md +++ b/packages/vectors/CHANGELOG.md @@ -3,6 +3,94 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [4.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/vectors@3.3.1...@thi.ng/vectors@4.0.0) (2019-11-09) + + +### Bug Fixes + +* **vectors:** fix normalizeS2/3/4 ([f048393](https://github.com/thi-ng/umbrella/commit/f04839355c90e991b0a8970af469119283454637)) +* **vectors:** fix out args in mixCubic/mixQuadratic ([d02dae6](https://github.com/thi-ng/umbrella/commit/d02dae6b4bad2d026dec96c865292778e2c50ba2)) +* **vectors:** update random2/3/4 to return new vec if none given ([a0be4d4](https://github.com/thi-ng/umbrella/commit/a0be4d4a288c61e7860990bb3c5b6992af30552c)) + + +### Code Refactoring + +* **vectors:** rename strided-scalar op suffixes (SN => NS) ([66258d8](https://github.com/thi-ng/umbrella/commit/66258d8b096de2a49d2f801a5329a07e7ef97c56)) + + +### Features + +* **vectors:** add fill(), add MultiVecOp.impl(), update vop() ([21ff930](https://github.com/thi-ng/umbrella/commit/21ff930e3c902051ed937e9294d71dd25688d729)) +* **vectors:** add mixCubicHermite versions & tangent fns ([b382d25](https://github.com/thi-ng/umbrella/commit/b382d25e65d6371e6b76219fd2909ac991933db4)) +* **vectors:** add more strided vec ops, refactor templates ([ca91fa9](https://github.com/thi-ng/umbrella/commit/ca91fa92c5720f361291c0672a9af4f79b3eafa6)) +* **vectors:** add new intoBuffer(), move fns for wrapped versions ([53581f1](https://github.com/thi-ng/umbrella/commit/53581f16effb42a1b3cc9aac8bd438880aaf7c97)) +* **vectors:** add strided random ops, types, defHofOpS() codegen ([1e46f5a](https://github.com/thi-ng/umbrella/commit/1e46f5aa6ad6d64bef5afdd7baf2d218e4547d1d)) +* **vectors:** add strided rotate ops ([4f2b5a7](https://github.com/thi-ng/umbrella/commit/4f2b5a72948774966c5580bdf33f75b913b9f460)) +* **vectors:** update readme ([f16bb45](https://github.com/thi-ng/umbrella/commit/f16bb4567eb293e56eabd6c1fb6969e1217598e0)) + + +### Performance Improvements + +* **vectors:** minor optimization for 0-index Vec2/3/4 accessors ([a7c561d](https://github.com/thi-ng/umbrella/commit/a7c561df31d7466676a48880f1ae1083d8938397)) + + +### BREAKING CHANGES + +* **vectors:** setSN2/3/4 => setSN2/3/4 + + + + + +## [3.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/vectors@3.3.0...@thi.ng/vectors@3.3.1) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/vectors + + + + + +# [3.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/vectors@3.2.0...@thi.ng/vectors@3.3.0) (2019-08-21) + + +### Features + +* **vectors:** add isNaN(), isInf() vec ops, update readme ([ed60d09](https://github.com/thi-ng/umbrella/commit/ed60d09)) + + + + + +# [3.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/vectors@3.1.1...@thi.ng/vectors@3.2.0) (2019-08-17) + + +### Features + +* **vectors:** add atan_2/22/23/24, update readme ([e9b156b](https://github.com/thi-ng/umbrella/commit/e9b156b)) + + + + + +## [3.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/vectors@3.1.0...@thi.ng/vectors@3.1.1) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/vectors + + + + + +# [3.1.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/vectors@3.0.3...@thi.ng/vectors@3.1.0) (2019-07-31) + + +### Features + +* **vectors:** add new bvec ops & types, update readme ([931ee43](https://github.com/thi-ng/umbrella/commit/931ee43)) + + + + + ## [3.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/vectors@3.0.2...@thi.ng/vectors@3.0.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/vectors diff --git a/packages/vectors/README.md b/packages/vectors/README.md index 744343ac6b..d42cb3550f 100644 --- a/packages/vectors/README.md +++ b/packages/vectors/README.md @@ -23,7 +23,7 @@ This project is part of the - [Component swizzling](#component-swizzling) - [Vector creation](#vector-creation) - [Basic vector math](#basic-vector-math) - - [Multiply-add](#multiply-add) + - [Combined operations](#combined-operations) - [Constraints](#constraints) - [Cross product](#cross-product) - [Dot product](#dot-product) @@ -52,7 +52,7 @@ This project is part of the Likely the most comprehensive vector library for TypeScript / JavaScript currently available. -This package provides **600+ largely code generated functions** and +This package provides **~720 largely code generated functions** and supporting types to perform vector operations on fixed and arbitrary-length vectors, both packed and strided (i.e. where individual vector components are not successive array elements, for example in SOA @@ -66,7 +66,7 @@ ops for signed & unsigned integer vectors. - Small & fast: The vast majority of functions are code generated with fixed-sized versions not using any loops. Minified + gzipped, the - entire package is ~9.2KB. + entire package is ~10.1KB (though you'll hardly ever use all functions). - Unified API: Any `ArrayLike` type can be used as vector containers (e.g. JS arrays, typed arrays, custom impls). Most functions are implemented as multi-methods, dispatching to any potentially optimized @@ -107,8 +107,10 @@ ops for signed & unsigned integer vectors. - [@thi.ng/color](https://github.com/thi-ng/umbrella/tree/master/packages/color) - vector based color operations / conversions - [@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/master/packages/geom) - 2D/3D geometry types & operations +- [@thi.ng/imgui](https://github.com/thi-ng/umbrella/tree/master/packages/imgui) - immediate mode GUI - [@thi.ng/matrices](https://github.com/thi-ng/umbrella/tree/master/packages/matrices) - 2x2, 2x3, 3x3, 4x4 matrix & quaternion ops -- [@thi.ng/shader-ast](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast) +- [@thi.ng/shader-ast](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast) - Shader DSL & cross-compilation +- [@thi.ng/shader-ast-js](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast-js) - JS code generator for shader-ast - [@thi.ng/vector-pools](https://github.com/thi-ng/umbrella/tree/master/packages/vector-pools) - operations on memory mapped data ## Installation @@ -136,7 +138,8 @@ import * as v from "@thi.ng/vectors"; v.add([], [1, 2, 3, 4], [10, 20, 30, 40]); // [11, 22, 33, 44] -// mutable addition (if first arg is null) +// mutable addition +// (if first arg (output) is null writes result to 2nd arg) a = [1, 2, 3]; v.add(null, a, a); // [2, 4, 6] @@ -153,6 +156,12 @@ v.maddN([], [10, 20], 0.5, [1, 2]); v.addN([], gvec([0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0], 3, 1, 4), 10); // [11, 12, 13] +// or operate on raw arrays directly... +// here the last 4 args define: +// out index, src index, out stride, src stride +v.addNS3(null, [0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0], 10, 1, 1, 4, 4) +// [0, 11, 0, 0, 0, 12, 0, 0, 0, 13, 0, 0, 0] + v.dist([1, 2], [100, 200]); // 221.37072977247917 @@ -179,8 +188,9 @@ v.hash([1, 2, 3]) ### Breaking changes in v3.0.0 -- to avoid confusion, the arg order of `madd` and `maddN` functions has - been updated to be compatible with the OpenCL `mad` function +- to avoid confusion, the arg order of `madd` and `maddN` functions have + been updated to be compatible with the OpenCL `mad` function and to + generally follow the expanded name, i.e. multiply-add: - `madd([], a, b, c)`: before `a + b * c`, now: `a * b + c` - `maddN([], a, b, n)` => `maddN([], a, n, b)` (i.e. `a * n + b`) - rename `perpendicularLeft2` => `perpendicularCCW` @@ -192,44 +202,27 @@ v.hash([1, 2, 3]) Wherever possible, each operation comes in different variations. All fixed size versions use optimized, loop-free implementations. -| Suffix | Description | Example | -|-----------------|----------------------------------------|---------------------------------------------------| -| none | arbitrary length vector arg(s) | `add([], [1,2], [10,20])` | -| | | => `[11,22]` | -| 2 | 2d vector arg(s) | `add2([], [1,2], [10,20])` | -| | | => `[11,22]` | -| 3 | 3d vector arg(s) | `add3([], [1,2,3], [10,20,30])` | -| | | => `[11,22,33]` | -| 4 | 4d vector arg(s) | `add4([], [1,2,3,4], [10,20,30,40])` | -| | | => `[11,22,33,44]` | -| N2 | 2d vector & scalar | `addN2([], [1,2], 10)` | -| | | => `[11,12]` | -| N3 | 3d vector & scalar | `addN3([], [1,2,3], 10)` | -| | | => `[11,12,13]` | -| N4 | 4d vector & scalar | `addN4([], [1,2,3,4], 10)` | -| | | => `[11,12,13,14]` | -| I | arbitrary len, signed int vec | `addI([], [-1,2], [10,-20])` | -| | | => `[9,-18]` | -| U | arbitrary len, unsigned int vec | `addU([], [1,2], [10,20])` | -| | | => `[11,22]` | -| I2 / I3 / I4 | fixed size signed int vec | `bitnotI3([], [10,-20,30], [7,7,7])` | -| | | => `[-11,19,-31]` | -| U2 / U3 / U4 | fixed size signed int vec | `bitnotU3([], [10,20,30], [7,7,7])` | -| | | => `[4294967285,4294967275,4294967265]` | -| NI / NU | arbitrary len, signed int vec & scalar | `addNI([], [1,-2,3], 10)` | -| | | => `[11,8,13]` | -| NI2 / NI3 / NI4 | fixed size signed int vec & scalar | `addNI3([], [1,-2,3], 10)` | -| | | => `[11,8,13]` | -| NU2 / NU3 / NU4 | fixed size unsigned int vec & scalar | `addNU3([], [1,2,3], 10)` | -| | | => `[11,12,13]` | -| S2 / S3 / S4 | fixed size strided vec into unstrided | `addS2([], [0,1,0,2], [10,0,20,0], 0,1,0, 1,2,2)` | -| | | => `[11,22]` | -| SN2 / SN3 / SN4 | fixed size strided vec & scalar | `setSN2([0,0,0,0], 10, 1, 2)` | -| | | => `[0,10,0,10]` | -| C | arbitrary len vec, component wise args | `setC([], 1,2,3,4)` | -| | | => `[1,2,3,4]` | -| C2 / C3 / C4 | fixed size vec, component wise args | `setC4([], 1,2,3,4)` | -| | | => `[1,2,3,4]` | +| Suffix | Description | +|-----------------|----------------------------------------| +| none | arbitrary length vector arg(s) | +| 2 | 2d vector arg(s) | +| 3 | 3d vector arg(s) | +| 4 | 4d vector arg(s) | +| N2 | 2d vector(s) & scalar | +| N3 | 3d vector(s) & scalar | +| N4 | 4d vector(s) & scalar | +| I | arbitrary len, signed int vec | +| U | arbitrary len, unsigned int vec | +| I2 / I3 / I4 | fixed size signed int vec | +| U2 / U3 / U4 | fixed size signed int vec | +| NI / NU | arbitrary len, signed int vec & scalar | +| NI2 / NI3 / NI4 | fixed size signed int vec & scalar | +| NU2 / NU3 / NU4 | fixed size unsigned int vec & scalar | +| S2 / S3 / S4 | fixed size strided vec | +| NS2 / NS3 / NS4 | fixed size strided vec & scalar | +| C | arbitrary len vec, component wise args | +| C2 / C3 / C4 | fixed size vec, component wise args | +| CS2 / CS3 / CS4 | fixed size strided vec, component args | ### Constants @@ -248,7 +241,8 @@ fixed size versions use optimized, loop-free implementations. - `setC` / `setC2` / `setC3` / `setC4` / `setC6` - `setN` / `setN2` / `setN3` / `setN4` - `setS` / `setS2` / `setS3` / `setS4` -- `setSN2` / `setSN3` / `setSN4` +- `setCS2` / `setCS3` / `setCS4` +- `setNS2` / `setNS3` / `setNS4` - `copy` - `empty` - `one` @@ -287,149 +281,168 @@ Vanilla vector (array) factories: Component wise op with 2 input vectors: -- `add` / `add2` / `add3` / `add4` -- `div` / `div2` / `div3` / `div4` -- `mul` / `mul2` / `mul3` / `mul4` -- `sub` / `sub2` / `sub3` / `sub4` -- `fmod` / `fmod2` / `fmod3` / `fmod4` (GLSL behavior) -- `mod` / `mod2` / `mod3` / `mod4` (JS modulo) -- `pow` / `pow2` / `pow3` / `pow4` - -#### Integer vector - -- `addI` / `addI2` / `addI3` / `addI4` -- `addU` / `addU2` / `addU3` / `addU4` -- `divI` / `divI2` / `divI3` / `divI4` -- `divU` / `divU2` / `divU3` / `divU4` -- `mulI` / `mulI2` / `mulI3` / `mulI4` -- `mulU` / `mulU2` / `mulU3` / `mulU4` -- `subI` / `subI2` / `subI3` / `subI4` -- `subU` / `subU2` / `subU3` / `subU4` +| Function | Generic | Fixed | Strided | Int | Comments | +|----------|---------|-------|---------|--------------|-----------------| +| `add` | βœ“ | 2-4 | S2-S4 | I2-I4, U2-U4 | | +| `div` | βœ“ | 2-4 | S2-S4 | I2-I4, U2-U4 | | +| `mul` | βœ“ | 2-4 | S2-S4 | I2-I4, U2-U4 | | +| `sub` | βœ“ | 2-4 | S2-S4 | I2-I4, U2-U4 | | +| `fmod` | βœ“ | 2-4 | | | (GLSL behavior) | +| `mod` | βœ“ | 2-4 | | | (JS behavior) | +| `pow` | βœ“ | 2-4 | | | | + +```ts +// generic +add([], [1, 2, 3, 4, 5], [10, 20, 30, 40, 50]); +// [11, 22, 33, 44, 55] + +// fixed size & packed +add2([], [1, 2], [10, 20]) +// [11, 22] + +// unsigned int +addU2([], [1, -2], [-10, 20]) +// [4294967287, 18] + +// strided +addS2([], [1,0,2,0], [0,10,0,0,0,20], 0, 0, 1, 1, 2, 4) +// [11, 22] +``` #### Vector / scalar Component wise op with one input vector and single scalar: -- `addN` / `addN2` / `addN3` / `addN4` -- `divN` / `divN2` / `divN3` / `divN4` -- `mulN` / `mulN2` / `mulN3` / `mulN4` -- `subN` / `subN2` / `subN3` / `subN4` -- `neg` - same as `mulN(out, v, -1)` -- `fmodN` / `fmodN2` / `fmodN3` / `fmodN4` (GLSL behavior) -- `modN` / `modN2` / `modN3` / `modN4` (JS modulo) -- `powN` / `powN2` / `powN3` / `powN4` - -#### Integer vector / scalar - -- `addNI` / `addNI2` / `addNI3` / `addNI4` -- `addNU` / `addNU2` / `addNU3` / `addNU4` -- `divNI` / `divNI2` / `divNI3` / `divNI4` -- `divNU` / `divNU2` / `divNU3` / `divNU4` -- `mulNI` / `mulNI2` / `mulNI3` / `mulNI4` -- `mulNU` / `mulNU2` / `mulNU3` / `mulNU4` -- `subNI` / `subNI2` / `subNI3` / `subNI4` -- `subNU` / `subNU2` / `subNU3` / `subNU4` - -#### Strided vectors - -Functions for memory mapped, strided vectors (without requiring wrappers): - -- `addS2` / `addS3` / `addS4` -- `divS2` / `divS3` / `divS4` -- `mulS2` / `mulS3` / `mulS4` -- `subS2` / `subS3` / `subS4` - -### Multiply-add - -- `addm` / `addm2` / `addm3` / `addm4` -- `addmN` / `addmN2` / `addmN3` / `addmN4` -- `addW2` / `addW3` / `addW4` / `addW5` -- `madd` / `madd2` / `madd3` / `madd4` -- `maddN` / `maddN2` / `maddN3` / `maddN4` -- `subm` / `subm2` / `subm3` / `subm4` -- `submN` / `submN2` / `submN3` / `submN4` +| Function | Generic | Fixed | Strided | Int | Comments | +|----------|---------|-------|---------|--------------|----------------------------| +| `addN` | βœ“ | 2-4 | S2-S4 | I2-I4, U2-U4 | | +| `divN` | βœ“ | 2-4 | S2-S4 | I2-I4, U2-U4 | | +| `mulN` | βœ“ | 2-4 | S2-S4 | I2-I4, U2-U4 | | +| `subN` | βœ“ | 2-4 | S2-S4 | I2-I4, U2-U4 | | +| `neg` | βœ“ | | | | same as `mulN(out, v, -1)` | +| `fmodN` | βœ“ | 2-4 | | | (GLSL behavior) | +| `modN` | βœ“ | 2-4 | | | (JS behavior) | +| `powN` | βœ“ | 2-4 | | | | + +### Combined operations + +| Function | Generic | Fixed | Strided | Int | Comments | +|----------|---------|-------|---------|-----|-------------| +| `addm` | βœ“ | 2-4 | S2-S4 | | (a + b) * c | +| `addmN` | βœ“ | 2-4 | S2-S4 | | (a + b) * n | +| `madd` | βœ“ | 2-4 | S2-S4 | | a * n + c | +| `maddN` | βœ“ | 2-4 | S2-S4 | | a * n + b | +| `msub` | βœ“ | 2-4 | S2-S4 | | a * n - c | +| `msubN` | βœ“ | 2-4 | S2-S4 | | a * n - b | +| `subm` | βœ“ | 2-4 | S2-S4 | | (a - b) * c | +| `submN` | βœ“ | 2-4 | S2-S4 | | (a - b) * n | ### Constraints -- `clamp` -- `clamp2` / `clamp3` / `clamp4` -- `clampN` / `clampN2` / `clampN3` / `clampN4` -- `clamp01` / `clamp01_2` / `clamp01_3` / `clamp01_4` -- `clamp11` / `clamp11_2` / `clamp11_3` / `clamp11_4` -- `max` / `max2` / `max3` / `max4` -- `min` / `min2` / `min3` / `min4` +| Function | Generic | Fixed | Strided | Int | Comments | +|-----------|---------|---------|---------|-----|----------------------| +| `clamp` | βœ“ | 2-4 | | | `min(max(a, b), c)` | +| `clampN` | βœ“ | 2-4 | | | `min(max(a, n), m)` | +| `clamp01` | βœ“ | _2 - _4 | | | `min(max(a, 0), 1)` | +| `clamp11` | βœ“ | _2 - _4 | | | `min(max(a, -1), 1)` | +| `max` | βœ“ | 2-4 | | | `max(a, b)` | +| `min` | βœ“ | 2-4 | | | `min(a, b)` | ### Cross product -- `cross2` -- `cross3` -- `orthoNormal3` -- `signedArea2` +| Function | Generic | Fixed | Strided | Int | Comments | +|---------------|---------|-------|---------|-----|---------------------------| +| `cross` | | 2, 3 | S2, S3 | | 2D version returns scalar | +| `orthoNormal` | | 3 | | | | +| `signedArea` | | 2 | | | | ### Dot product -- `dot` -- `dot2` / `dot3` / `dot4` -- `dotC4` / `dotC6` / `dotC8` -- `dotS2` / `dotS3` / `dotS4` +| Function | Generic | Fixed | Strided | Cwise | Comments | +|----------|---------|-------|---------|------------|----------| +| `dot` | βœ“ | 2-4 | S2-S4 | C4, C6, C8 | | ### Interpolation -- `fit` / `fit2` / `fit3` / `fit4` -- `fit01` / `fit01_2` / `fit01_3` / `fit01_4` -- `fit11` / `fit11_2` / `fit11_3` / `fit11_4` -- `mix` / `mix2` / `mix3` / `mix4` -- `mixN` / `mixN2` / `mixN3` / `mixN4` -- `mixBilinear` / `mixBilinear2` / `mixBilinear3` / `mixBilinear4` -- `mixCubic` -- `mixQuadratic` -- `smoothStep` / `smoothStep2` / `smoothStep3` / `smoothStep4` -- `step` / `step2` / `step3` / `step4` +| Function | Generic | Fixed | Strided | Int | Comments | +|----------------|---------|---------|---------|-----|----------| +| `fit` | βœ“ | 2-4 | | | | +| `fit01` | βœ“ | _2 - _4 | | | | +| `fit11` | βœ“ | _2 - _4 | | | | +| `mix` | βœ“ | 2-4 | S2 - S4 | | | +| `mixN` | βœ“ | 2-4 | S2 - S4 | | | +| `mixBilinear` | βœ“ | 2-4 | | | | +| `mixCubic` | βœ“ | | | | | +| `mixQuadratic` | βœ“ | | | | | +| `smoothStep` | βœ“ | 2-4 | | | | +| `step` | βœ“ | 2-4 | | | | ### Normalization / magnitude -- `limit` -- `mag` -- `magSq` / `magSq2` / `magSq3` / `magSq4` -- `normalize` +| Function | Generic | Fixed | Strided | Int | Comments | +|-------------|---------|-------|---------|-----|----------------------| +| `limit` | βœ“ | | | | | +| `mag` | βœ“ | | S2-S4 | | | +| `magSq` | βœ“ | 2-4 | S2-S4 | | | +| `normalize` | βœ“ | | S2-S4 | | w/ opt target length | ### Distances -- `dist` -- `distSq` / `distSq2` / `distSq3` / `distSq4` -- `distChebyshev` / `distChebyshev2` / `distChebyshev3` / `distChebyshev4` -- `distManhattan` / `distManhattan2` / `distManhattan3` / `distManhattan4` +| Function | Generic | Fixed | Strided | Int | Comments | +|-----------------|---------|-------|---------|-----|----------| +| `dist` | βœ“ | | | | | +| `distSq` | βœ“ | 2-4 | | | | +| `distChebyshev` | βœ“ | 2-4 | | | | +| `distManhattan` | βœ“ | 2-4 | | | | ### Orientation -- `angleBetween2` / `angleBetween3` -- `angleRatio` -- `bisect2` -- `degrees` / `degrees2` / `degrees3` / `degrees4` -- `direction` -- `faceForward` -- `heading` / `headingXY` / `headingXZ` / `headingYZ` -- `headingSegment` / `headingSegmentXY` / `headingSegmentXZ` / `headingSegmentYZ` -- `normalLeft2` / `normalRight2` -- `perpendicularLeft2` / `perpendicularRight2` -- `project` -- `radians` / `radians2` / `radians3` / `radians4` -- `reflect` -- `refract` +| Function | Generic | Fixed | Strided | Int | Comments | +|--------------------|---------|-------|---------|-----|--------------------------| +| `angleBetween` | | 2, 3 | | | | +| `angleRatio` | βœ“ | | | | | +| `atan_2` | βœ“ | 2-4 | | | `Math.atan2(y, x)` | +| `bisect` | | 2 | | | | +| `degrees` | βœ“ | 2-4 | | | | +| `direction` | βœ“ | | | | normalize(b - a) | +| `faceForward` | βœ“ | | | | | +| `heading` | βœ“ | | | | alias `headingXY` | +| `headingXY` | βœ“ | | | | | +| `headingXZ` | βœ“ | | | | | +| `headingYZ` | βœ“ | | | | | +| `headingSegment` | βœ“ | | | | alias `headingSegmentXY` | +| `headingSegmentXY` | βœ“ | | | | | +| `headingSegmentXZ` | βœ“ | | | | | +| `headingSegmentYZ` | βœ“ | | | | | +| `normalCCW` | | | | | 2D only | +| `normalCW` | | | | | 2D only | +| `perpendicularCCW` | | | | | 2D only | +| `perpendicularCW` | | | | | 2D only | +| `project` | βœ“ | | | | | +| `radians` | βœ“ | 2-4 | | | | +| `reflect` | βœ“ | | | | | +| `refract` | βœ“ | | | | | ### Rotations (Also see rotation matrices provided by [@thi.ng/matrices](https://github.com/thi-ng/umbrella/tree/master/packages/matrices)) -- `rotateAroundAxis3` -- `rotateAroundPoint2` -- `rotateX` \ `rotateY` \ `rotateZ` +| Function | Generic | Fixed | Strided | Int | Comments | +|-----------------------|---------|-------|---------|-----|---------------------| +| `rotationAroundAxis` | | 3 | | | | +| `rotationAroundPoint` | | 2 | | | | +| `rotate` | | | S2 | | alias for `rotateZ` | +| `rotateX` | | | S3 | | | +| `rotateY` | | | S3 | | | +| `rotateZ` | | | S3 | | | ### Polar / cartesian conversion -- `cartesian` / `cartesian2` / `cartesian3` -- `polar` / `polar2` / `polar3` +| Function | Generic | Fixed | Strided | Int | Comments | +|-------------|---------|-------|---------|-----|------------| +| `cartesian` | βœ“ | 2, 3 | | | 2D/3D only | +| `polar` | βœ“ | 2, 3 | | | 2D/3D only | ### Randomness @@ -437,41 +450,44 @@ All ops support custom PRNG impls based on the [@thi.ng/random](https://github.com/thi-ng/umbrella/tree/master/packages/random) `IRandom` interface and use `Math.random` by default: -- `jitter` -- `randMinMax` / `randMinMax2` / `randMinMax3` / `randMinMax4` -- `randNorm` -- `random` / `random2` / `random3` / `random4` +| Function | Generic | Fixed | Strided | Int | Comments | +|--------------|---------|-------|---------|-----|----------| +| `jitter` | βœ“ | | | | | +| `randMinMax` | βœ“ | 2-4 | S2-S4 | | | +| `randNorm` | βœ“ | | S2-S4 | | | +| `random` | βœ“ | 2-4 | S2-S4 | | | ### Unary vector math ops -- `abs` / `abs2` / `abs3` / `abs4` -- `acos` / `acos2` / `acos3` / `acos4` -- `asin` / `asin2` / `asin3` / `asin4` -- `atan` / `atan2` / `atan3` / `atan4` -- `ceil` / `ceil2` / `ceil3` / `ceil4` -- `cos` / `cos2` / `cos3` / `cos4` -- `cosh` / `cosh2` / `cosh3` / `cosh4` -- `exp` / `exp2` / `exp3` / `exp4` -- `floor` / `floor2` / `floor3` / `floor4` -- `fmod` / `fmod2` / `fmod3` / `fmod4` (C / GLSL modulo) -- `fract` / `fract2` / `fract3` / `fract4` -- `fromHomogeneous` / `fromHomogeneous3` / `fromHomogeneous4` -- `invert` / `invert2` / `invert3` / `invert4` -- `invSqrt` / `invSqrt2` / `invSqrt3` / `invSqrt4` -- `log` / `log2` / `log3` / `log4` -- `major` / `major2` / `major3` / `major4` -- `minor` / `minor2` / `minor3` / `minor4` -- `mod` / `mod2` / `mod3` / `mod4` (JS modulo) -- `round` / `round2` / `round3` / `round4` -- `sign` / `sign2` / `sign3` / `sign4` -- `sin` / `sin2` / `sin3` / `sin4` -- `sinh` / `sinh2` / `sinh3` / `sinh4` -- `sqrt` / `sqrt2` / `sqrt3` / `sqrt4` -- `sum` / `sum2` / `sum3` / `sum4` -- `tan` / `tan2` / `tan3` / `tan4` -- `tanh` / `tanh2` / `tanh3` / `tanh4` -- `trunc` / `trunc2` / `trunc3` / `trunc4` -- `wrap` / `wrap2` / `wrap3` / `wrap4` +| Function | Generic | Fixed | Strided | Int | Comments | +|-------------------|---------|-------|---------|-----|--------------------| +| `abs` | βœ“ | 2-4 | | | | +| `acos` | βœ“ | 2-4 | | | | +| `asin` | βœ“ | 2-4 | | | | +| `atan` | βœ“ | 2-4 | | | `Math.atan(y / x)` | +| `ceil` | βœ“ | 2-4 | | | | +| `cos` | βœ“ | 2-4 | | | | +| `cosh` | βœ“ | 2-4 | | | | +| `exp` | βœ“ | 2-4 | | | | +| `floor` | βœ“ | 2-4 | | | | +| `fract` | βœ“ | 2-4 | | | | +| `fromHomogeneous` | βœ“ | 3, 4 | | | 3D/4D only | +| `invert` | βœ“ | 2-4 | | | | +| `invSqrt` | βœ“ | 2-4 | | | | +| `isInf` | βœ“ | 2-4 | | | | +| `isNaN` | βœ“ | 2-4 | | | | +| `log` | βœ“ | 2-4 | | | | +| `major` | βœ“ | 2-4 | | | | +| `minor` | βœ“ | 2-4 | | | | +| `round` | βœ“ | 2-4 | | | | +| `sign` | βœ“ | 2-4 | | | | +| `sin` | βœ“ | 2-4 | | | | +| `sinh` | βœ“ | 2-4 | | | | +| `sqrt` | βœ“ | 2-4 | | | | +| `sum` | βœ“ | 2-4 | | | | +| `tan` | βœ“ | 2-4 | | | | +| `trunc` | βœ“ | 2-4 | | | | +| `wrap` | βœ“ | 2-4 | | | | ### Vector array batch processing @@ -491,41 +507,45 @@ Functions to transform flat / strided buffers w/ vector operations: Arguments are assumed to be signed / unsigned ints. Results will be forced accordingly. -- `bitAndI` / `bitAndI2` / `bitAndI3` / `bitAndI4` -- `bitAndU` / `bitAndU2` / `bitAndU3` / `bitAndU4` -- `bitAndNI` / `bitAndNI2` / `bitAndNI3` / `bitAndNI4` -- `bitAndNU` / `bitAndNU2` / `bitAndNU3` / `bitAndNU4` -- `bitNotI` / `bitNotI2` / `bitNotI3` / `bitNotI4` (unary) -- `bitNotU` / `bitNotU2` / `bitNotU3` / `bitNotU4` (unary) -- `bitOrI` / `bitOrI2` / `bitOrI3` / `bitOrI4` -- `bitOrU` / `bitOrU2` / `bitOrU3` / `bitOrU4` -- `bitOrNI` / `bitOrNI2` / `bitOrNI3` / `bitOrNI4` -- `bitOrNU` / `bitOrNU2` / `bitOrNU3` / `bitOrNU4` -- `bitXorI` / `bitXorI2` / `bitXorI3` / `bitXorI4` -- `bitXorU` / `bitXorU2` / `bitXorU3` / `bitXorU4` -- `bitXorNI` / `bitXorNI2` / `bitXorNI3` / `bitXorNI4` -- `bitXorNU` / `bitXorNU2` / `bitXorNU3` / `bitXorNU4` -- `lshiftI` / `lshiftI4` /`lshiftI4` / `lshiftI4` -- `lshiftU` / `lshiftU4` /`lshiftU4` / `lshiftU4` -- `rshiftI` / `rshiftI2` /`rshiftI3` / `rshiftI4` -- `rshiftU` / `rshiftU2` /`rshiftU3` / `rshiftU4` +| Function | Generic | Fixed | Strided | Int | Comments | +|-----------|---------|-------|---------|--------------|----------| +| `bitAnd` | βœ“ | | | I2-I4, U2-U4 | | +| `bitAndN` | βœ“ | | | I2-I4, U2-U4 | | +| `bitNot` | βœ“ | | | I2-I4, U2-U4 | | +| `bitOr` | βœ“ | | | I2-I4, U2-U4 | | +| `bitOrN` | βœ“ | | | I2-I4, U2-U4 | | +| `bitXor` | βœ“ | | | I2-I4, U2-U4 | | +| `bitXorN` | βœ“ | | | I2-I4, U2-U4 | | +| `lshift` | βœ“ | | | I2-I4, U2-U4 | | +| `rshift` | βœ“ | | | I2-I4, U2-U4 | | +| `lshiftN` | βœ“ | | | I2-I4, U2-U4 | | +| `rshiftN` | βœ“ | | | I2-I4, U2-U4 | | ### Boolean vector logic -- `logicAnd` / `logicAnd2` / `logicAnd3` / `logicAnd4` -- `logicOr` / `logicOr2` / `logicOr3` / `logicOr4` -- `logicNot` / `logicNot2` / `logicNot3` / `logicNot4` +| Function | Generic | Fixed | Strided | Int | Comments | +|-------------|---------|-------|---------|-----|----------| +| `logicAnd` | βœ“ | 2-4 | | | | +| `logicAndN` | βœ“ | 2-4 | | | | +| `logicOr` | βœ“ | 2-4 | | | | +| `logicOrN` | βœ“ | 2-4 | | | | +| `logicNot` | βœ“ | 2-4 | | | | +| `every` | βœ“ | 2-4 | | | | +| `some` | βœ“ | 2-4 | | | | ### Componentwise comparisons All resulting in boolean vectors: -- `lt` / `lt2` / `lt3`/ `lt4` -- `lte` / `lte2` / `lte3`/ `lte4` -- `gt` / `gt2` / `gt3`/ `gt4` -- `gte` / `gte2` / `gte3`/ `gte4` -- `eq` / `eq2` / `eq3`/ `eq4` -- `neq` / `neq2` / `neq3`/ `neq4` +| Function | Generic | Fixed | Strided | Int | Comments | +|----------|---------|-------|---------|-----|----------| +| `eq` | βœ“ | 2-4 | | | | +| `lt` | βœ“ | 2-4 | | | | +| `lte` | βœ“ | 2-4 | | | | +| `gt` | βœ“ | 2-4 | | | | +| `gte` | βœ“ | 2-4 | | | | +| `neq` | βœ“ | 2-4 | | | | + ### Hashing diff --git a/packages/vectors/package.json b/packages/vectors/package.json index 7b42ed22e1..1454d72c4d 100644 --- a/packages/vectors/package.json +++ b/packages/vectors/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/vectors", - "version": "3.0.3", + "version": "4.0.0", "description": "Optimized 2d/3d/4d and arbitrary length vector operations", "module": "./index.js", "main": "./lib/index.js", @@ -29,19 +29,19 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/binary": "^1.1.0", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/math": "^1.4.2", - "@thi.ng/memoize": "^1.1.2", - "@thi.ng/random": "^1.1.10", - "@thi.ng/transducers": "^5.4.2" + "@thi.ng/api": "^6.5.0", + "@thi.ng/binary": "^1.1.1", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/math": "^1.5.0", + "@thi.ng/memoize": "^1.1.5", + "@thi.ng/random": "^1.1.13", + "@thi.ng/transducers": "^6.0.0" }, "keywords": [ "2D", diff --git a/packages/vectors/src/addm.ts b/packages/vectors/src/addm.ts index 5dbb43a2e2..e36c10be8d 100644 --- a/packages/vectors/src/addm.ts +++ b/packages/vectors/src/addm.ts @@ -1,6 +1,6 @@ import { MultiVecOpVVV, VecOpVVV } from "./api"; import { ARGS_VVV, defOp } from "./internal/codegen"; -import { ADDM } from "./internal/templates"; +import { MATH2 } from "./internal/templates"; /** * Returns `out = (a + b) * c`. @@ -9,6 +9,6 @@ import { ADDM } from "./internal/templates"; * @see subm */ export const [addm, addm2, addm3, addm4] = defOp( - ADDM, + MATH2("+", "*"), ARGS_VVV ); diff --git a/packages/vectors/src/addmn.ts b/packages/vectors/src/addmn.ts index f2d66cc51e..f7d0f6469e 100644 --- a/packages/vectors/src/addmn.ts +++ b/packages/vectors/src/addmn.ts @@ -1,11 +1,11 @@ import { MultiVecOpVVN, VecOpVVN } from "./api"; import { ARGS_VVN, defOp } from "./internal/codegen"; -import { ADDM_N } from "./internal/templates"; +import { MATH2_N } from "./internal/templates"; /** * Returns `out = (a + b) * n`. */ export const [addmN, addmN2, addmN3, addmN4] = defOp( - ADDM_N, + MATH2_N("+", "*"), ARGS_VVN ); diff --git a/packages/vectors/src/addmns.ts b/packages/vectors/src/addmns.ts new file mode 100644 index 0000000000..9d82bab932 --- /dev/null +++ b/packages/vectors/src/addmns.ts @@ -0,0 +1,8 @@ +import { VecOpSVNV } from "./api"; +import { ARGS_VVN, defOpS, SARGS_VV } from "./internal/codegen"; +import { MATH2_N } from "./internal/templates"; + +export const [addmNS2, addmNS3, addmNS4] = defOpS( + MATH2_N("+", "*"), + `${ARGS_VVN},${SARGS_VV}` +); diff --git a/packages/vectors/src/addms.ts b/packages/vectors/src/addms.ts new file mode 100644 index 0000000000..75b7e40a00 --- /dev/null +++ b/packages/vectors/src/addms.ts @@ -0,0 +1,9 @@ +import { VecOpSVVV } from "./api"; +import { ARGS_VVV, defOpS, SARGS_VVV } from "./internal/codegen"; +import { MATH2 } from "./internal/templates"; + +export const [addmS2, addmS3, addmS4] = defOpS( + MATH2("+", "*"), + `${ARGS_VVV},${SARGS_VVV}`, + ARGS_VVV +); diff --git a/packages/vectors/src/addns.ts b/packages/vectors/src/addns.ts new file mode 100644 index 0000000000..19d4559549 --- /dev/null +++ b/packages/vectors/src/addns.ts @@ -0,0 +1,14 @@ +import { VecOpSVN } from "./api"; +import { + ARGS_V, + ARGS_VN, + defOpS, + SARGS_V +} from "./internal/codegen"; +import { MATH_N } from "./internal/templates"; + +export const [addNS2, addNS3, addNS4] = defOpS( + MATH_N("+"), + `${ARGS_VN},${SARGS_V}`, + ARGS_V +); diff --git a/packages/vectors/src/api.ts b/packages/vectors/src/api.ts index 1773d9a605..054de1426e 100644 --- a/packages/vectors/src/api.ts +++ b/packages/vectors/src/api.ts @@ -54,8 +54,27 @@ export interface VectorConstructor { } export interface MultiVecOp { + /** + * Adds / overwrites implementation for given vector size. + * + * @param dim + * @param op + */ add(dim: number, op: VOP): VOP; + /** + * Adds / overwrites default implementation (SHOULD support + * arbitrary vector sizes). + * + * @param op + */ default(op: VOP): VOP; + /** + * Returns implementation for given vector size or default + * implementation. + * + * @param dim + */ + impl(dim: number): VOP; } export type VecPair = [Vec, Vec]; @@ -102,6 +121,17 @@ export type VecOpSV = ( so?: number, sa?: number ) => Vec; + +export type VecOpSVN = ( + out: Vec | null, + a: ReadonlyVec, + n: number, + io?: number, + ia?: number, + so?: number, + sa?: number +) => Vec; + export type VecOpSVV = ( out: Vec | null, a: ReadonlyVec, @@ -113,6 +143,50 @@ export type VecOpSVV = ( sa?: number, sb?: number ) => Vec; + +export type VecOpSVNV = ( + out: Vec | null, + a: ReadonlyVec, + n: number, + b: ReadonlyVec, + io?: number, + ia?: number, + ib?: number, + so?: number, + sa?: number, + sb?: number +) => Vec; + +export type VecOpSVVN = ( + out: Vec | null, + a: ReadonlyVec, + b: ReadonlyVec, + n: number, + io?: number, + ia?: number, + ib?: number, + so?: number, + sa?: number, + sb?: number +) => Vec; + +export type VecOpSVVV = ( + out: Vec | null, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + io?: number, + ia?: number, + ib?: number, + ic?: number, + so?: number, + sa?: number, + sb?: number, + sc?: number +) => Vec; + +export type VecOpSRoV = (a: ReadonlyVec, ia?: number, sa?: number) => T; + export type VecOpSRoVV = ( a: ReadonlyVec, b: ReadonlyVec, @@ -122,6 +196,33 @@ export type VecOpSRoVV = ( sb?: number ) => T; +export type VecOpSVO = ( + out: Vec | null, + a: ReadonlyVec, + b?: T, + io?: number, + ia?: number, + so?: number, + sa?: number +) => Vec; + +export type VecOpSOO = ( + a: Vec | null, + opt1?: A, + opt2?: B, + ia?: number, + sa?: number +) => Vec; + +export type VecOpSOOO = ( + a: Vec | null, + opt1?: A, + opt2?: B, + opt3?: C, + ia?: number, + sa?: number +) => Vec; + export interface MultiVecOpV extends VecOpV, MultiVecOp {} export interface MultiVecOpN extends VecOpN, MultiVecOp {} export interface MultiVecOpVV extends VecOpVV, MultiVecOp {} @@ -155,13 +256,24 @@ export interface MultiVecOpRoVVO extends VecOpRoVVO, MultiVecOp> {} +export type BVecOpRoV = Fn; export type BVecOpV = Fn2; export type BVecOpVV = Fn3; +export type BVecOpVN = Fn3; + +export type ToBVecOpV = Fn2; export type CompareOp = Fn3; export interface MultiBVecOpV extends BVecOpV, MultiVecOp {} export interface MultiBVecOpVV extends BVecOpVV, MultiVecOp {} +export interface MultiBVecOpVN extends BVecOpVN, MultiVecOp {} +export interface MultiBVecOpRoV + extends BVecOpRoV, + MultiVecOp> {} + +export interface MultiToBVecOpV extends ToBVecOpV, MultiVecOp {} + export interface MultiCompareOp extends CompareOp, MultiVecOp {} const mi = -Infinity; diff --git a/packages/vectors/src/atan.ts b/packages/vectors/src/atan.ts index 690649bef4..aac62e4dcd 100644 --- a/packages/vectors/src/atan.ts +++ b/packages/vectors/src/atan.ts @@ -1,6 +1,17 @@ -import { MultiVecOpV, VecOpV } from "./api"; -import { defFnOp } from "./internal/codegen"; +import { + MultiVecOpV, + MultiVecOpVV, + VecOpV, + VecOpVV +} from "./api"; +import { ARGS_VV, defFnOp, defOp } from "./internal/codegen"; +import { FN2 } from "./internal/templates"; export const [atan, atan2, atan3, atan4] = defFnOp( "Math.atan" ); + +export const [atan_2, atan_22, atan_23, atan_24] = defOp( + FN2("Math.atan2"), + ARGS_VV +); diff --git a/packages/vectors/src/buffer.ts b/packages/vectors/src/buffer.ts new file mode 100644 index 0000000000..71b0061576 --- /dev/null +++ b/packages/vectors/src/buffer.ts @@ -0,0 +1,115 @@ +import { + SIZEOF, + Type, + typedArray, + TYPEDARRAY_CTORS, + TypedArrayTypeMap +} from "@thi.ng/api"; +import { + ReadonlyVec, + Vec, + VecOpSV, + VectorConstructor +} from "./api"; + +/** + * Takes an `ArrayBuffer` and creates a number of typed array vector + * views of `type` with given `size` (number of elements per vector) and + * spacing. `byteOffset` defines the start offset for the first vector + * and `byteStride` the number of bytes between resulting vectors + * (defaults to `size * SIZEOF[type]`). It's user's responsibility to + * ensure these two values are compatible with the chosen array type + * (i.e. for `Type.F32`, these MUST be multiples of 4). + * + * ``` + * mapBuffer(Type.F32, new ArrayBuffer(32), 4, 2) + * // [ + * // Float32Array [ 0, 0 ], + * // Float32Array [ 0, 0 ], + * // Float32Array [ 0, 0 ], + * // Float32Array [ 0, 0 ] + * // ] + * ``` + * + * @param type + * @param buf + * @param num + * @param size + * @param byteOffset + * @param byteStride + */ +export const mapBuffer = ( + type: T, + buf: ArrayBufferLike, + num: number, + size: number, + byteOffset = 0, + byteStride = size * SIZEOF[type] +) => { + const res: TypedArrayTypeMap[T][] = []; + const ctor = TYPEDARRAY_CTORS[type]; + for (; --num >= 0; byteOffset += byteStride) { + res.push(new ctor(buf, byteOffset, size)); + } + return res; +}; + +/** + * Writes given `src` vector values into mapped `ArrayBuffer` of stated + * `type` and from given offset & stride/spacing. + * + * @see mapBuffer + * + * @param type + * @param buf + * @param src + * @param byteOffset + * @param byteStride + */ +export const intoBuffer = ( + type: T, + buf: ArrayBufferLike, + src: Iterable, + byteOffset: number, + byteStride: number +) => { + const view = typedArray(type, buf); + const size = SIZEOF[type]; + byteOffset /= size; + byteStride /= size; + for (let x of src) { + view.set(x, byteOffset); + byteOffset += byteStride; + } +}; + +export const mapStridedBuffer = ( + ctor: VectorConstructor, + buf: Vec, + num: number, + start: number, + cstride: number, + estride: number +) => { + const res: T[] = []; + while (--num >= 0) { + res.push(new ctor(buf, start, cstride)); + start += estride; + } + return res; +}; + +export const intoStridedBuffer = ( + set: VecOpSV, + buf: Vec, + src: Iterable, + start: number, + cstride: number, + estride: number +) => { + for (let v of src) { + set(buf, v, start, 0, cstride, 1); + start += estride; + } + return buf; +}; diff --git a/packages/vectors/src/crosss.ts b/packages/vectors/src/crosss.ts new file mode 100644 index 0000000000..bf92a7b3f8 --- /dev/null +++ b/packages/vectors/src/crosss.ts @@ -0,0 +1,31 @@ +import { ReadonlyVec, Vec } from "./api"; +import { setCS3 } from "./setcs"; + +export const crossS2 = ( + a: ReadonlyVec, + b: ReadonlyVec, + ia = 0, + ib = 0, + sa = 1, + sb = 1 +) => a[ia] * b[ib + sb] - a[ia + sa] * b[ib]; + +export const crossS3 = ( + out: Vec | null, + a: ReadonlyVec, + b: ReadonlyVec, + io = 0, + ia = 0, + ib = 0, + so = 1, + sa = 1, + sb = 1 +) => + setCS3( + out || a, + a[ia + sa] * b[ib + 2 * sb] - a[ia + 2 * sa] * b[ib + sb], + a[ia + 2 * sa] * b[ib] - a[ia] * b[ib + 2 * sb], + a[ia] * b[ib + sb] - a[ia + sa] * b[ib], + io, + so + ); diff --git a/packages/vectors/src/divns.ts b/packages/vectors/src/divns.ts new file mode 100644 index 0000000000..388d41a5c0 --- /dev/null +++ b/packages/vectors/src/divns.ts @@ -0,0 +1,14 @@ +import { VecOpSVN } from "./api"; +import { + ARGS_V, + ARGS_VN, + defOpS, + SARGS_V +} from "./internal/codegen"; +import { MATH_N } from "./internal/templates"; + +export const [divNS2, divNS3, divNS4] = defOpS( + MATH_N("/"), + `${ARGS_VN},${SARGS_V}`, + ARGS_V +); diff --git a/packages/vectors/src/every.ts b/packages/vectors/src/every.ts new file mode 100644 index 0000000000..86f7645067 --- /dev/null +++ b/packages/vectors/src/every.ts @@ -0,0 +1,20 @@ +import { MultiBVecOpRoV } from "./api"; +import { vop } from "./internal/vop"; + +/** + * Returns returns true if all vector components in `v` are truthy. + * + * @param v + */ +export const every: MultiBVecOpRoV = vop(); + +every.default((v) => { + for (let i = v.length; --i >= 0; ) { + if (!v[i]) return false; + } + return true; +}); + +export const every2 = every.add(2, (a) => a[0] && a[1]); +export const every3 = every.add(3, (a) => a[0] && a[1] && a[2]); +export const every4 = every.add(4, (a) => a[0] && a[1] && a[2] && a[3]); diff --git a/packages/vectors/src/fill.ts b/packages/vectors/src/fill.ts new file mode 100644 index 0000000000..9cf0d59d74 --- /dev/null +++ b/packages/vectors/src/fill.ts @@ -0,0 +1,31 @@ +import { IVector } from "./api"; +import { mapV } from "./map"; +import { set } from "./set"; + +/** + * Fills Vec2/3/4 view based buffer, supporting arbitrary component and + * element layouts of both the input and output buffers. The `out` + * vector is used as write cursor over the underlying buffer and will be + * filled with the components of vector `v`. + * + * ``` + * fill( + * new Vec2(new Float32Array(12)), + * new Vec2([1, 2]), + * 3, // num elements + * 4 // stride + * ) + * // Float32Array [1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0] + * ``` + * + * @param out target vector / wrapped buffer + * @param v fill vector + * @param num number of elements to fill + * @param so output stride + */ +export const fill = ( + out: IVector, + v: IVector, + num: number, + so = out.length * out.stride +) => mapV(set.impl(v.length), out, v, num, so, 0); diff --git a/packages/vectors/src/fmod.ts b/packages/vectors/src/fmod.ts index 9c5a9f984e..2d8f90b777 100644 --- a/packages/vectors/src/fmod.ts +++ b/packages/vectors/src/fmod.ts @@ -5,7 +5,7 @@ import { FN2 } from "./internal/templates"; /** * This version of mod uses the same logic as in GLSL, whereas `mod` * merely uses JavaScript's `%` modulo operator, yielding different - * results for negative values. + * results for negative values, i.e. using the sign of the last arg. * * `a - b * floor(a/b)` * diff --git a/packages/vectors/src/gvec.ts b/packages/vectors/src/gvec.ts index c89100af82..81ac18bfb1 100644 --- a/packages/vectors/src/gvec.ts +++ b/packages/vectors/src/gvec.ts @@ -3,7 +3,7 @@ import { memoize1 } from "@thi.ng/memoize"; import { map, range } from "@thi.ng/transducers"; import { IVector, Vec } from "./api"; import { eqDeltaS } from "./eqdelta"; -import { values } from "./internal/vec-utils"; +import { stridedValues } from "./internal/vec-utils"; import { zeroes } from "./setn"; import { setS } from "./sets"; @@ -107,7 +107,7 @@ export const gvec = ( get(obj, id) { switch (id) { case Symbol.iterator: - return () => values(obj, size, offset, stride); + return () => stridedValues(obj, size, offset, stride); case SYM_L: return size; case SYM_B: @@ -127,7 +127,9 @@ export const gvec = ( eqDeltaS(buf, o, size, eps, offset, 0, stride, 1); case SYM_STR: return () => - JSON.stringify([...values(obj, size, offset, stride)]); + JSON.stringify([ + ...stridedValues(obj, size, offset, stride) + ]); default: const j = parseInt(id); return !isNaN(j) && j >= 0 && j < size diff --git a/packages/vectors/src/index.ts b/packages/vectors/src/index.ts index 2f86b069de..ccf59c1ede 100644 --- a/packages/vectors/src/index.ts +++ b/packages/vectors/src/index.ts @@ -17,8 +17,11 @@ export * from "./add"; export * from "./addi"; export * from "./addm"; export * from "./addmn"; +export * from "./addms"; +export * from "./addmns"; export * from "./addn"; export * from "./adds"; +export * from "./addns"; export * from "./angle-between"; export * from "./asin"; export * from "./atan"; @@ -27,6 +30,7 @@ export * from "./bit-and"; export * from "./bit-not"; export * from "./bit-or"; export * from "./bit-xor"; +export * from "./buffer"; export * from "./cartesian"; export * from "./ceil"; export * from "./clamp"; @@ -37,6 +41,7 @@ export * from "./copy"; export * from "./cos"; export * from "./cosh"; export * from "./cross"; +export * from "./crosss"; export * from "./degrees"; export * from "./direction"; export * from "./dist"; @@ -47,14 +52,17 @@ export * from "./div"; export * from "./divi"; export * from "./divn"; export * from "./divs"; +export * from "./divns"; export * from "./dot"; export * from "./dotc"; export * from "./dots"; export * from "./empty"; export * from "./eqdelta"; +export * from "./every"; export * from "./exp"; export * from "./exp_2"; export * from "./face-forward"; +export * from "./fill"; export * from "./fit"; export * from "./floor"; export * from "./fmod"; @@ -67,6 +75,8 @@ export * from "./heading-segment"; export * from "./homogeneous"; export * from "./invert"; export * from "./invsqrt"; +export * from "./is-inf"; +export * from "./is-nan"; export * from "./jitter"; export * from "./limit"; export * from "./log"; @@ -77,8 +87,12 @@ export * from "./logic-or"; export * from "./lshift"; export * from "./madd"; export * from "./maddn"; +export * from "./madds"; +export * from "./maddns"; export * from "./mag"; +export * from "./mags"; export * from "./magsq"; +export * from "./magsqs"; export * from "./major"; export * from "./map"; export * from "./max"; @@ -86,18 +100,27 @@ export * from "./min"; export * from "./minor"; export * from "./mix-bilinear"; export * from "./mix-cubic"; +export * from "./mix-hermite"; export * from "./mix-quadratic"; export * from "./mix"; export * from "./mixn"; +export * from "./mixs"; +export * from "./mixns"; export * from "./mod"; export * from "./modn"; +export * from "./msub"; +export * from "./msubn"; +export * from "./msubs"; +export * from "./msubns"; export * from "./mul"; export * from "./muli"; export * from "./muln"; export * from "./muls"; +export * from "./mulns"; export * from "./neg"; export * from "./normal"; export * from "./normalize"; +export * from "./normalizes"; export * from "./ortho-normal"; export * from "./perpendicular"; export * from "./polar"; @@ -106,24 +129,28 @@ export * from "./pown"; export * from "./project"; export * from "./radians"; export * from "./random"; +export * from "./randoms"; export * from "./reflect"; export * from "./refract"; export * from "./rotate-around-axis"; export * from "./rotate-around-point"; export * from "./rotate"; +export * from "./rotates"; export * from "./round"; export * from "./rshift"; export * from "./set"; export * from "./setc"; export * from "./setn"; export * from "./sets"; -export * from "./setsn"; +export * from "./setcs"; +export * from "./setns"; export * from "./setvn"; export * from "./setvv"; export * from "./sign"; export * from "./signed-area"; export * from "./sin"; export * from "./sinh"; +export * from "./some"; export * from "./sqrt"; export * from "./step"; export * from "./smoothstep"; @@ -131,8 +158,11 @@ export * from "./sub"; export * from "./subi"; export * from "./subm"; export * from "./submn"; +export * from "./subms"; +export * from "./submns"; export * from "./subn"; export * from "./subs"; +export * from "./subns"; export * from "./sum"; export * from "./swizzle"; export * from "./tan"; diff --git a/packages/vectors/src/internal/accessors.ts b/packages/vectors/src/internal/accessors.ts index e350c8218f..8584f27fc4 100644 --- a/packages/vectors/src/internal/accessors.ts +++ b/packages/vectors/src/internal/accessors.ts @@ -5,21 +5,30 @@ export const declareIndex = ( strided = true, defNumeric = true ) => { - const get = strided - ? function() { - return this.buf[this.offset + idx * this.stride]; - } - : function() { - return this.buf[this.offset + idx]; - }; - const set = strided - ? function(n: number) { - this.buf[this.offset + idx * this.stride] = n; - } - : function(n: number) { - this.buf[this.offset + idx] = n; - }; - + const get = + idx > 0 + ? strided + ? function() { + return this.buf[this.offset + idx * this.stride]; + } + : function() { + return this.buf[this.offset + idx]; + } + : function() { + return this.buf[this.offset]; + }; + const set = + idx > 0 + ? strided + ? function(n: number) { + this.buf[this.offset + idx * this.stride] = n; + } + : function(n: number) { + this.buf[this.offset + idx] = n; + } + : function(n: number) { + this.buf[this.offset] = n; + }; defNumeric && Object.defineProperty(proto, idx, { get, diff --git a/packages/vectors/src/internal/codegen.ts b/packages/vectors/src/internal/codegen.ts index 058c755e3f..af44d6040a 100644 --- a/packages/vectors/src/internal/codegen.ts +++ b/packages/vectors/src/internal/codegen.ts @@ -295,6 +295,31 @@ export const defOpS = ( ) ); +export const defHofOpS = ( + op: any, + tpl: Template, + args = `${ARGS_VV},${SARGS_VV}`, + syms = ARGS_VV, + ret = "o", + pre?: string, + sizes = [2, 3, 4] +): V[] => + sizes.map((dim) => + compileHOF( + dim, + [op], + tpl, + "op", + args, + syms, + ret, + "", + pre != null ? pre : defaultOut(ret, args), + "", + true + ) + ); + export const defMathOp = (op: string) => defOp(MATH(op)); export const defMathOpN = (op: string) => diff --git a/packages/vectors/src/internal/templates.ts b/packages/vectors/src/internal/templates.ts index 38b010ee65..15991f5828 100644 --- a/packages/vectors/src/internal/templates.ts +++ b/packages/vectors/src/internal/templates.ts @@ -5,6 +5,12 @@ export const MATH = (op: string): Template => ([o, a, b]) => `${o}=${a}${op}${b} // prettier-ignore export const MATH_N = (op: string): Template => ([o, a]) => `${o}=${a}${op}n;`; // prettier-ignore +export const MATH2 = (op1: string, op2: string): Template => ([o, a, b, c]) => `${o}=(${a}${op1}${b})${op2}${c};`; +// prettier-ignore +export const MATH2_N = (op1: string, op2: string): Template => ([o, a, b]) => `${o}=(${a}${op1}${b})${op2}n;`; +// prettier-ignore +export const MATH2A_N = (op1: string, op2: string): Template => ([o, a, b]) => `${o}=(${a}${op1}n)${op2}${b};`; +// prettier-ignore export const SIGNED = (op: string): Template => ([o, a, b]) => `${o}=(${a}${op}${b})|0;`; // prettier-ignore export const UNSIGNED = (op: string): Template => ([o, a, b]) => `${o}=(${a}${op}${b})>>>0;`; @@ -28,11 +34,5 @@ export const DOT_G: Template = ([a, b]) => `s+=${a}*${b};`; export const SET: Template = ([o, a]) => `${o}=${a};`; export const SET_N: Template = ([a]) => `${a}=n;`; -export const ADDM: Template = ([o, a, b, c]) => `${o}=(${a}+${b})*${c};`; -export const ADDM_N: Template = ([o, a, b]) => `${o}=(${a}+${b})*n;`; -export const MADD: Template = ([o, a, b, c]) => `${o}=${a}*${b}+${c};`; -export const MADD_N: Template = ([o, a, b]) => `${o}=${a}*n+${b};`; export const MIX: Template = ([o, a, b, c]) => `${o}=${a}+(${b}-${a})*${c};`; export const MIX_N: Template = ([o, a, b]) => `${o}=${a}+(${b}-${a})*n;`; -export const SUBM: Template = ([o, a, b, c]) => `${o}=(${a}-${b})*${c};`; -export const SUBM_N: Template = ([o, a, b]) => `${o}=(${a}-${b})*n;`; diff --git a/packages/vectors/src/internal/vec-utils.ts b/packages/vectors/src/internal/vec-utils.ts index 8deca305f7..28eb1722cc 100644 --- a/packages/vectors/src/internal/vec-utils.ts +++ b/packages/vectors/src/internal/vec-utils.ts @@ -1,35 +1,4 @@ -import { Vec, VecOpSV, VectorConstructor } from "../api"; - -export const mapBuffer = ( - ctor: VectorConstructor, - buf: Vec, - num: number, - start: number, - cstride: number, - estride: number -) => { - const res: T[] = []; - while (--num >= 0) { - res.push(new ctor(buf, start, cstride)); - start += estride; - } - return res; -}; - -export const intoBuffer = ( - set: VecOpSV, - buf: Vec, - src: Iterable, - start: number, - cstride: number, - estride: number -) => { - for (let v of src) { - set(buf, v, start, 0, cstride, 1); - start += estride; - } - return buf; -}; +import { Vec, VectorConstructor } from "../api"; export function* vecIterator( ctor: VectorConstructor, @@ -45,7 +14,12 @@ export function* vecIterator( } } -export function* values(buf: Vec, num: number, start: number, stride: number) { +export function* stridedValues( + buf: Vec, + num: number, + start: number, + stride: number +) { while (num-- > 0) { yield buf[start]; start += stride; diff --git a/packages/vectors/src/internal/vop.ts b/packages/vectors/src/internal/vop.ts index f2a0c6e28d..a9d45720e5 100644 --- a/packages/vectors/src/internal/vop.ts +++ b/packages/vectors/src/internal/vop.ts @@ -18,6 +18,7 @@ export const vop = (dispatch = 0) => { }; fn.add = (dim: number, fn: T) => (impls[dim] = fn); fn.default = (fn: T) => (fallback = fn); + fn.impl = (dim: number): T => impls[dim] || fallback; // fn.impls = impls; return fn; }; diff --git a/packages/vectors/src/is-inf.ts b/packages/vectors/src/is-inf.ts new file mode 100644 index 0000000000..24f770fbae --- /dev/null +++ b/packages/vectors/src/is-inf.ts @@ -0,0 +1,7 @@ +import { MultiToBVecOpV, ToBVecOpV } from "./api"; +import { defFnOp } from "./internal/codegen"; + +export const [isInf, isInf2, isInf3, isInf4] = defFnOp< + MultiToBVecOpV, + ToBVecOpV +>("!isFinite"); diff --git a/packages/vectors/src/is-nan.ts b/packages/vectors/src/is-nan.ts new file mode 100644 index 0000000000..97ae4805f2 --- /dev/null +++ b/packages/vectors/src/is-nan.ts @@ -0,0 +1,7 @@ +import { MultiToBVecOpV, ToBVecOpV } from "./api"; +import { defFnOp } from "./internal/codegen"; + +export const [isNaN, isNaN2, isNaN3, isNaN4] = defFnOp< + MultiToBVecOpV, + ToBVecOpV +>("isNaN"); diff --git a/packages/vectors/src/logic-and.ts b/packages/vectors/src/logic-and.ts index 4b0da9e389..0f83f13689 100644 --- a/packages/vectors/src/logic-and.ts +++ b/packages/vectors/src/logic-and.ts @@ -1,8 +1,18 @@ -import { BVecOpVV, MultiBVecOpVV } from "./api"; -import { defOp } from "./internal/codegen"; -import { MATH } from "./internal/templates"; +import { + BVecOpVN, + BVecOpVV, + MultiBVecOpVN, + MultiBVecOpVV +} from "./api"; +import { ARGS_VN, defOp } from "./internal/codegen"; +import { MATH, MATH_N } from "./internal/templates"; export const [logicAnd, logicAnd2, logicAnd3, logicAnd4] = defOp< MultiBVecOpVV, BVecOpVV >(MATH("&&")); + +export const [logicAndN, logicAndN2, logicAndN3, logicAndN4] = defOp< + MultiBVecOpVN, + BVecOpVN +>(MATH_N("&&"), ARGS_VN); diff --git a/packages/vectors/src/logic-or.ts b/packages/vectors/src/logic-or.ts index 641d7253c0..ee307d9cf9 100644 --- a/packages/vectors/src/logic-or.ts +++ b/packages/vectors/src/logic-or.ts @@ -1,8 +1,18 @@ -import { BVecOpVV, MultiBVecOpVV } from "./api"; -import { defOp } from "./internal/codegen"; -import { MATH } from "./internal/templates"; +import { + BVecOpVN, + BVecOpVV, + MultiBVecOpVN, + MultiBVecOpVV +} from "./api"; +import { ARGS_VN, defOp } from "./internal/codegen"; +import { MATH, MATH_N } from "./internal/templates"; export const [logicOr, logicOr2, logicOr3, logicOr4] = defOp< MultiBVecOpVV, BVecOpVV >(MATH("||")); + +export const [logicOrN, logicOrN2, logicOrN3, logicOrN4] = defOp< + MultiBVecOpVN, + BVecOpVN +>(MATH_N("||"), ARGS_VN); diff --git a/packages/vectors/src/madd.ts b/packages/vectors/src/madd.ts index dff87dff41..e15a86736f 100644 --- a/packages/vectors/src/madd.ts +++ b/packages/vectors/src/madd.ts @@ -1,6 +1,6 @@ import { MultiVecOpVVV, VecOpVVV } from "./api"; import { ARGS_VVV, defOp } from "./internal/codegen"; -import { MADD } from "./internal/templates"; +import { MATH2 } from "./internal/templates"; /** * Returns `out = a * b + c`. @@ -14,6 +14,6 @@ import { MADD } from "./internal/templates"; * @param c */ export const [madd, madd2, madd3, madd4] = defOp( - MADD, + MATH2("*", "+"), ARGS_VVV ); diff --git a/packages/vectors/src/maddn.ts b/packages/vectors/src/maddn.ts index 44d21a6ffa..75c46d9da9 100644 --- a/packages/vectors/src/maddn.ts +++ b/packages/vectors/src/maddn.ts @@ -1,6 +1,6 @@ import { MultiVecOpVNV, VecOpVNV } from "./api"; import { ARGS_VNV, ARGS_VV, defOp } from "./internal/codegen"; -import { MADD_N } from "./internal/templates"; +import { MATH2A_N } from "./internal/templates"; /** * Returns `out = a * n + b`. @@ -11,7 +11,7 @@ import { MADD_N } from "./internal/templates"; * @param b vec */ export const [maddN, maddN2, maddN3, maddN4] = defOp( - MADD_N, + MATH2A_N("*", "+"), ARGS_VNV, ARGS_VV ); diff --git a/packages/vectors/src/maddns.ts b/packages/vectors/src/maddns.ts new file mode 100644 index 0000000000..bed3fa89b2 --- /dev/null +++ b/packages/vectors/src/maddns.ts @@ -0,0 +1,8 @@ +import { VecOpSVNV } from "./api"; +import { ARGS_VNV, defOpS, SARGS_VV } from "./internal/codegen"; +import { MATH2A_N } from "./internal/templates"; + +export const [maddNS2, maddNS3, maddNS4] = defOpS( + MATH2A_N("*", "+"), + `${ARGS_VNV},${SARGS_VV}` +); diff --git a/packages/vectors/src/madds.ts b/packages/vectors/src/madds.ts new file mode 100644 index 0000000000..5a8da14482 --- /dev/null +++ b/packages/vectors/src/madds.ts @@ -0,0 +1,9 @@ +import { VecOpSVVV } from "./api"; +import { ARGS_VVV, defOpS, SARGS_VVV } from "./internal/codegen"; +import { MATH2 } from "./internal/templates"; + +export const [maddS2, maddS3, maddS4] = defOpS( + MATH2("*", "+"), + `${ARGS_VVV},${SARGS_VVV}`, + ARGS_VVV +); diff --git a/packages/vectors/src/mags.ts b/packages/vectors/src/mags.ts new file mode 100644 index 0000000000..ab11337fcf --- /dev/null +++ b/packages/vectors/src/mags.ts @@ -0,0 +1,11 @@ +import { VecOpSRoV } from "./api"; +import { dotS2, dotS3, dotS4 } from "./dots"; + +export const magS2: VecOpSRoV = (a, ia, sa) => + Math.sqrt(dotS2(a, a, ia, ia, sa, sa)); + +export const magS3: VecOpSRoV = (a, ia, sa) => + Math.sqrt(dotS3(a, a, ia, ia, sa, sa)); + +export const magS4: VecOpSRoV = (a, ia, sa) => + Math.sqrt(dotS4(a, a, ia, ia, sa, sa)); diff --git a/packages/vectors/src/magsqs.ts b/packages/vectors/src/magsqs.ts new file mode 100644 index 0000000000..d037fcaa17 --- /dev/null +++ b/packages/vectors/src/magsqs.ts @@ -0,0 +1,11 @@ +import { VecOpSRoV } from "./api"; +import { dotS2, dotS3, dotS4 } from "./dots"; + +export const magSqS2: VecOpSRoV = (a, ia, sa) => + dotS2(a, a, ia, ia, sa, sa); + +export const magSqS3: VecOpSRoV = (a, ia, sa) => + dotS3(a, a, ia, ia, sa, sa); + +export const magSqS4: VecOpSRoV = (a, ia, sa) => + dotS4(a, a, ia, ia, sa, sa); diff --git a/packages/vectors/src/mix-cubic.ts b/packages/vectors/src/mix-cubic.ts index 0f080bc3d7..e434aa590c 100644 --- a/packages/vectors/src/mix-cubic.ts +++ b/packages/vectors/src/mix-cubic.ts @@ -1,8 +1,8 @@ -import { ReadonlyVec, Vec } from "./api"; import { addW4 } from "./addw"; +import { ReadonlyVec, Vec } from "./api"; export const mixCubic = ( - out: Vec, + out: Vec | null, a: ReadonlyVec, b: ReadonlyVec, c: ReadonlyVec, diff --git a/packages/vectors/src/mix-hermite.ts b/packages/vectors/src/mix-hermite.ts new file mode 100644 index 0000000000..700af56a0a --- /dev/null +++ b/packages/vectors/src/mix-hermite.ts @@ -0,0 +1,151 @@ +import { addmN } from "./addmn"; +import { addW4 } from "./addw"; +import { ReadonlyVec, Vec } from "./api"; +import { submN } from "./submn"; + +/** + * Vector version of thi.ng/math `mixCubicHermite`. + * + * @param out + * @param a + * @param ta + * @param b + * @param tb + * @param t + */ +export const mixCubicHermite = ( + out: Vec | null, + a: ReadonlyVec, + ta: ReadonlyVec, + b: ReadonlyVec, + tb: ReadonlyVec, + t: number +) => { + const s = t - 1; + const t2 = t * t; + const s2 = s * s; + return addW4( + out, + a, + ta, + b, + tb, + (1 + 2 * t) * s2, + t * s2, + t2 * (3 - 2 * t), + t2 * s + ); +}; + +/** + * Convenience version of `mixCubicHermite`, using 4 input points and + * `tangentCardinal` to compute the tangents for points `b` and `c` + * (with optional `scale`, default 0.5). Interpolated result point is + * that of `b` and `c`, with `a` and `d` only being used for tangent + * calculations. + * + * If `out` is null, stores result in `b`. + * + * @param out + * @param a + * @param b + * @param c + * @param d + * @param t + * @param scale + */ +export const mixHermiteCardinal = ( + out: Vec | null, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + d: ReadonlyVec, + t: number, + scale = 0.5 +) => + mixCubicHermite( + out, + b, + tangentCardinal([], a, c, scale), + c, + tangentCardinal([], b, d, scale), + t + ); + +/** + * Convenience version of `mixCubicHermite`, using 4 input points and + * `tangentDiff3` to compute the tangents for points `b` and `c`. + * Interpolated result point is that of `b` and `c`, with `a` and `d` + * only being used for tangent calculations. + * + * If `out` is null, stores result in `b`. + * + * @param out + * @param a + * @param b + * @param c + * @param d + * @param t + */ +export const mixHermiteDiff3 = ( + out: Vec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + d: ReadonlyVec, + t: number +) => + mixCubicHermite( + out, + b, + tangentDiff3([], a, b, c), + c, + tangentDiff3([], b, c, d), + t + ); + +/** + * Vector version of thi.ng/math `tangentCardinal`. + * + * @param out + * @param prev + * @param next + * @param scale + * @param ta + * @param tc + */ +export const tangentCardinal = ( + out: Vec, + prev: ReadonlyVec, + next: ReadonlyVec, + scale = 0.5, + ta = 0, + tc = 2 +) => submN(out, next, prev, scale / (tc - ta)); + +/** + * Vector version of thi.ng/math `tangentDiff3`. + * + * @param out + * @param prev + * @param curr + * @param next + * @param ta + * @param tb + * @param tc + */ +export const tangentDiff3 = ( + out: Vec, + prev: ReadonlyVec, + curr: ReadonlyVec, + next: ReadonlyVec, + ta = 0, + tb = 1, + tc = 2 +) => + addmN( + out, + submN(out, curr, prev, 1 / (tb - ta)), + submN([], next, curr, 1 / (tc - tb)), + 0.5 + ); diff --git a/packages/vectors/src/mix-quadratic.ts b/packages/vectors/src/mix-quadratic.ts index 7f52868697..823be39e64 100644 --- a/packages/vectors/src/mix-quadratic.ts +++ b/packages/vectors/src/mix-quadratic.ts @@ -2,7 +2,7 @@ import { addW3 } from "./addw"; import { ReadonlyVec, Vec } from "./api"; export const mixQuadratic = ( - out: Vec, + out: Vec | null, a: ReadonlyVec, b: ReadonlyVec, c: ReadonlyVec, diff --git a/packages/vectors/src/mixns.ts b/packages/vectors/src/mixns.ts new file mode 100644 index 0000000000..a1153726b4 --- /dev/null +++ b/packages/vectors/src/mixns.ts @@ -0,0 +1,8 @@ +import { VecOpSVVN } from "./api"; +import { ARGS_VVN, defOpS, SARGS_VV } from "./internal/codegen"; +import { MIX_N } from "./internal/templates"; + +export const [mixNS2, mixNS3, mixNS4] = defOpS( + MIX_N, + `${ARGS_VVN},${SARGS_VV}` +); diff --git a/packages/vectors/src/mixs.ts b/packages/vectors/src/mixs.ts new file mode 100644 index 0000000000..f36dd471eb --- /dev/null +++ b/packages/vectors/src/mixs.ts @@ -0,0 +1,9 @@ +import { VecOpSVVV } from "./api"; +import { ARGS_VVV, defOpS, SARGS_VVV } from "./internal/codegen"; +import { MIX } from "./internal/templates"; + +export const [mixS2, mixS3, mixS4] = defOpS( + MIX, + `${ARGS_VVV},${SARGS_VVV}`, + ARGS_VVV +); diff --git a/packages/vectors/src/msub.ts b/packages/vectors/src/msub.ts new file mode 100644 index 0000000000..7186a41c08 --- /dev/null +++ b/packages/vectors/src/msub.ts @@ -0,0 +1,19 @@ +import { MultiVecOpVVV, VecOpVVV } from "./api"; +import { ARGS_VVV, defOp } from "./internal/codegen"; +import { MATH2 } from "./internal/templates"; + +/** + * Returns `out = a * b + c`. + * + * @see addm + * @see maddN + * + * @param out + * @param a + * @param b + * @param c + */ +export const [msub, msub2, msub3, msub4] = defOp( + MATH2("*", "-"), + ARGS_VVV +); diff --git a/packages/vectors/src/msubn.ts b/packages/vectors/src/msubn.ts new file mode 100644 index 0000000000..eece790f99 --- /dev/null +++ b/packages/vectors/src/msubn.ts @@ -0,0 +1,17 @@ +import { MultiVecOpVNV, VecOpVNV } from "./api"; +import { ARGS_VNV, ARGS_VV, defOp } from "./internal/codegen"; +import { MATH2A_N } from "./internal/templates"; + +/** + * Returns `out = a * n + b`. + * + * @param out vec + * @param a vec + * @param n scalar + * @param b vec + */ +export const [msubN, msubN2, msubN3, msubN4] = defOp( + MATH2A_N("*", "-"), + ARGS_VNV, + ARGS_VV +); diff --git a/packages/vectors/src/msubns.ts b/packages/vectors/src/msubns.ts new file mode 100644 index 0000000000..7f7f291146 --- /dev/null +++ b/packages/vectors/src/msubns.ts @@ -0,0 +1,8 @@ +import { VecOpSVNV } from "./api"; +import { ARGS_VNV, defOpS, SARGS_VV } from "./internal/codegen"; +import { MATH2A_N } from "./internal/templates"; + +export const [msubNS2, msubNS3, msubNS4] = defOpS( + MATH2A_N("*", "-"), + `${ARGS_VNV},${SARGS_VV}` +); diff --git a/packages/vectors/src/msubs.ts b/packages/vectors/src/msubs.ts new file mode 100644 index 0000000000..22fbfe3bfa --- /dev/null +++ b/packages/vectors/src/msubs.ts @@ -0,0 +1,9 @@ +import { VecOpSVVV } from "./api"; +import { ARGS_VVV, defOpS, SARGS_VVV } from "./internal/codegen"; +import { MATH2 } from "./internal/templates"; + +export const [msubS2, msubS3, msubS4] = defOpS( + MATH2("*", "-"), + `${ARGS_VVV},${SARGS_VVV}`, + ARGS_VVV +); diff --git a/packages/vectors/src/mulns.ts b/packages/vectors/src/mulns.ts new file mode 100644 index 0000000000..dc9850ac3e --- /dev/null +++ b/packages/vectors/src/mulns.ts @@ -0,0 +1,14 @@ +import { VecOpSVN } from "./api"; +import { + ARGS_V, + ARGS_VN, + defOpS, + SARGS_V +} from "./internal/codegen"; +import { MATH_N } from "./internal/templates"; + +export const [mulNS2, mulNS3, mulNS4] = defOpS( + MATH_N("*"), + `${ARGS_VN},${SARGS_V}`, + ARGS_V +); diff --git a/packages/vectors/src/normalizes.ts b/packages/vectors/src/normalizes.ts new file mode 100644 index 0000000000..8833d3d25c --- /dev/null +++ b/packages/vectors/src/normalizes.ts @@ -0,0 +1,83 @@ +import { EPS } from "@thi.ng/math"; +import { VecOpSVO } from "./api"; +import { magS2, magS3, magS4 } from "./mags"; +import { mulNS2, mulNS3, mulNS4 } from "./mulns"; +import { setS2, setS3, setS4 } from "./sets"; + +/** + * Normalizes vector to given (optional) length (default: 1). If `out` + * is null, modifies `v` in place. + * + * @param out + * @param v + * @param n + */ +export const normalizeS2: VecOpSVO = ( + out, + v, + n = 1, + io = 0, + ia = 0, + so = 1, + sa = 1 +) => { + !out && (out = v); + const m = magS2(v, ia, sa); + return m >= EPS + ? mulNS2(out, v, n / m, io, ia, so, sa) + : out !== v + ? setS2(out, v, io, ia, so, sa) + : out; +}; + +/** + * Normalizes vector to given (optional) length (default: 1). If `out` + * is null, modifies `v` in place. + * + * @param out + * @param v + * @param n + */ +export const normalizeS3: VecOpSVO = ( + out, + v, + n = 1, + io = 0, + ia = 0, + so = 1, + sa = 1 +) => { + !out && (out = v); + const m = magS3(v, ia, sa); + return m >= EPS + ? mulNS3(out, v, n / m, io, ia, so, sa) + : out !== v + ? setS3(out, v, io, ia, so, sa) + : out; +}; + +/** + * Normalizes vector to given (optional) length (default: 1). If `out` + * is null, modifies `v` in place. + * + * @param out + * @param v + * @param n + */ +export const normalizeS4: VecOpSVO = ( + out, + v, + n = 1, + io = 0, + ia = 0, + so = 1, + sa = 1 +) => { + !out && (out = v); + const m = magS4(v, ia, sa); + return m >= EPS + ? mulNS4(out, v, n / m, io, ia, so, sa) + : out !== v + ? setS4(out, v, io, ia, so, sa) + : out; +}; diff --git a/packages/vectors/src/random.ts b/packages/vectors/src/random.ts index b3848e8534..1c284f018f 100644 --- a/packages/vectors/src/random.ts +++ b/packages/vectors/src/random.ts @@ -11,6 +11,7 @@ import { normalize } from "./normalize"; /** * Sets `v` to random vector, with each component in interval `[n..m)`. * If no `rnd` instance is given, uses `SYSTEM`, i.e. `Math.random`. + * Creates new vector if `v` is null. * * @param v * @param n default -1 @@ -27,7 +28,7 @@ export const [random, random2, random3, random4] = defHofOp< "a", "a", 0, - "" + "!a && (a=[]);" ); /** @@ -38,10 +39,8 @@ export const [random, random2, random3, random4] = defHofOp< * @param n * @param rnd */ -export const randNorm = (v: Vec | null, n = 1, rnd: IRandom = SYSTEM) => { - v = random(v, -1, 1, rnd); - return normalize(v, v, n); -}; +export const randNorm = (v: Vec | null, n = 1, rnd: IRandom = SYSTEM) => + normalize((v = random(v, -1, 1, rnd)), v, n); /** * Sets `out` to random vector with each component in the semi-open diff --git a/packages/vectors/src/randoms.ts b/packages/vectors/src/randoms.ts new file mode 100644 index 0000000000..6973128ed4 --- /dev/null +++ b/packages/vectors/src/randoms.ts @@ -0,0 +1,69 @@ +import { IRandom, SYSTEM } from "@thi.ng/random"; +import { + ReadonlyVec, + VecOpSOO, + VecOpSOOO, + VecOpSVO +} from "./api"; +import { defHofOpS, SARGS_VV } from "./internal/codegen"; +import { normalizeS2, normalizeS3, normalizeS4 } from "./normalizes"; + +/** + * Randomizes `v` with each component in interval `[n..m)`. If no `rnd` + * instance is given, uses `SYSTEM`, i.e. `Math.random`. + * + * @param v + * @param n default -1 + * @param m default 1 + * @param rnd + * @param ia + * @param sa + */ +export const [randomS2, randomS3, randomS4] = defHofOpS< + VecOpSOOO +>( + SYSTEM, + ([a]) => `${a}=rnd.minmax(n,m);`, + "a,n=-1,m=1,rnd=op,ia=0,sa=1", + "a", + "a", + "!a && (a=[]);" +); + +const $norm = ( + normalize: VecOpSVO, + random: VecOpSOOO +): VecOpSOO => (a, n = 1, rnd?, ia = 0, sa = 1) => + normalize((a = random(a, -1, 1, rnd, ia, sa)), a, n, ia, ia, sa, sa); + +/** + * Sets `v` to random vector, normalized to length `n` (default: 1). If no + * `rnd` instance is given, uses `SYSTEM`, i.e. `Math.random`. + * + * @param v + * @param n + * @param rnd + */ +export const randNormS2 = $norm(normalizeS2, randomS2); + +export const randNormS3 = $norm(normalizeS3, randomS3); + +export const randNormS4 = $norm(normalizeS4, randomS4); + +/** + * Sets `out` to random vector with each component in the semi-open + * interval defined by [min,max). + * + * @param out + * @param min + * @param max + * @param rnd + */ +export const [randMinMaxS2, randMinMaxS3, randMinMaxS4] = defHofOpS< + VecOpSOOO +>( + SYSTEM, + ([o, a, b]) => `${o}=rnd.minmax(${a},${b});`, + `o,a,b,rnd=op,${SARGS_VV}`, + "o,a,b" +); diff --git a/packages/vectors/src/rotate.ts b/packages/vectors/src/rotate.ts index 1483d72bad..8f1971baec 100644 --- a/packages/vectors/src/rotate.ts +++ b/packages/vectors/src/rotate.ts @@ -15,3 +15,8 @@ const _rotate = (u: number, v: number): VecOpVN => (out, a, theta) => { export const rotateX = _rotate(1, 2); export const rotateY = _rotate(2, 0); export const rotateZ = _rotate(0, 1); + +/** + * Alias for `rotateZ` (e.g. for 2D use cases) + */ +export const rotate = rotateZ; diff --git a/packages/vectors/src/rotates.ts b/packages/vectors/src/rotates.ts new file mode 100644 index 0000000000..319c7c6b04 --- /dev/null +++ b/packages/vectors/src/rotates.ts @@ -0,0 +1,27 @@ +import { VecOpSV, VecOpSVN } from "./api"; +import { setS2, setS3 } from "./sets"; + +const _rotate = (set: VecOpSV, u: number, v: number): VecOpSVN => ( + out, + a, + theta, + io = 0, + ia = 0, + so = 1, + sa = 1 +) => { + out ? out !== a && set(out, a, io, ia, so, sa) : (out = a); + const s = Math.sin(theta); + const c = Math.cos(theta); + const x = a[ia + u * sa]; + const y = a[ia + v * sa]; + out[io + u * so] = x * c - y * s; + out[io + v * so] = x * s + y * c; + return out; +}; + +export const rotateS2 = _rotate(setS2, 0, 1); + +export const rotateXS3 = _rotate(setS3, 1, 2); +export const rotateYS3 = _rotate(setS3, 2, 0); +export const rotateZS3 = _rotate(setS3, 0, 1); diff --git a/packages/vectors/src/setcs.ts b/packages/vectors/src/setcs.ts new file mode 100644 index 0000000000..dc80b9ddb2 --- /dev/null +++ b/packages/vectors/src/setcs.ts @@ -0,0 +1,41 @@ +import { Vec } from "./api"; + +export const setCS2 = ( + out: Vec | null, + x: number, + y: number, + io = 0, + so = 1 +) => (!out && (out = []), (out[io] = x), (out[io + so] = y), out); + +export const setCS3 = ( + out: Vec | null, + x: number, + y: number, + z: number, + io = 0, + so = 1 +) => ( + !out && (out = []), + (out[io] = x), + (out[io + so] = y), + (out[io + 2 * so] = z), + out +); + +export const setCS4 = ( + out: Vec | null, + x: number, + y: number, + z: number, + w: number, + io = 0, + so = 1 +) => ( + !out && (out = []), + (out[io] = x), + (out[io + so] = y), + (out[io + 2 * so] = z), + (out[io + 3 * so] = w), + out +); diff --git a/packages/vectors/src/setsn.ts b/packages/vectors/src/setns.ts similarity index 76% rename from packages/vectors/src/setsn.ts rename to packages/vectors/src/setns.ts index c3c51c9147..9033034648 100644 --- a/packages/vectors/src/setsn.ts +++ b/packages/vectors/src/setns.ts @@ -2,7 +2,7 @@ import { VecOpSV } from "./api"; import { defOpS } from "./internal/codegen"; import { SET_N } from "./internal/templates"; -export const [setSN2, setSN3, setSN4] = defOpS( +export const [setNS2, setNS3, setNS4] = defOpS( SET_N, "o,n,io=0,so=1", "o", diff --git a/packages/vectors/src/some.ts b/packages/vectors/src/some.ts new file mode 100644 index 0000000000..7834545728 --- /dev/null +++ b/packages/vectors/src/some.ts @@ -0,0 +1,21 @@ +import { MultiBVecOpRoV } from "./api"; +import { vop } from "./internal/vop"; + +/** + * Returns returns true if at least one vector component in `v` is + * truthy. + * + * @param v + */ +export const some: MultiBVecOpRoV = vop(); + +some.default((v) => { + for (let i = v.length; --i >= 0; ) { + if (v[i]) return true; + } + return false; +}); + +export const some2 = some.add(2, (a) => a[0] || a[1]); +export const some3 = some.add(3, (a) => a[0] || a[1] || a[2]); +export const some4 = some.add(4, (a) => a[0] || a[1] || a[2] || a[3]); diff --git a/packages/vectors/src/subm.ts b/packages/vectors/src/subm.ts index fe2d8a46a0..40a78563f3 100644 --- a/packages/vectors/src/subm.ts +++ b/packages/vectors/src/subm.ts @@ -1,6 +1,6 @@ import { MultiVecOpVVV, VecOpVVV } from "./api"; import { ARGS_VVV, defOp } from "./internal/codegen"; -import { SUBM } from "./internal/templates"; +import { MATH2 } from "./internal/templates"; /** * Returns `out = (a - b) * c`. @@ -9,6 +9,6 @@ import { SUBM } from "./internal/templates"; * @see addm */ export const [subm, subm2, subm3, subm4] = defOp( - SUBM, + MATH2("-", "*"), ARGS_VVV ); diff --git a/packages/vectors/src/submn.ts b/packages/vectors/src/submn.ts index f8d033572d..e7a6b5c9d6 100644 --- a/packages/vectors/src/submn.ts +++ b/packages/vectors/src/submn.ts @@ -1,11 +1,11 @@ import { MultiVecOpVVN, VecOpVVN } from "./api"; import { ARGS_VVN, defOp } from "./internal/codegen"; -import { SUBM_N } from "./internal/templates"; +import { MATH2_N } from "./internal/templates"; /** * Returns `out = (a - b) * n`. */ export const [submN, submN2, submN3, submN4] = defOp( - SUBM_N, + MATH2_N("-", "*"), ARGS_VVN ); diff --git a/packages/vectors/src/submns.ts b/packages/vectors/src/submns.ts new file mode 100644 index 0000000000..bc4e41ae25 --- /dev/null +++ b/packages/vectors/src/submns.ts @@ -0,0 +1,8 @@ +import { VecOpSVNV } from "./api"; +import { ARGS_VVN, defOpS, SARGS_VV } from "./internal/codegen"; +import { MATH2_N } from "./internal/templates"; + +export const [submNS2, submNS3, submNS4] = defOpS( + MATH2_N("-", "*"), + `${ARGS_VVN},${SARGS_VV}` +); diff --git a/packages/vectors/src/subms.ts b/packages/vectors/src/subms.ts new file mode 100644 index 0000000000..681757fbbc --- /dev/null +++ b/packages/vectors/src/subms.ts @@ -0,0 +1,9 @@ +import { VecOpSVVV } from "./api"; +import { ARGS_VVV, defOpS, SARGS_VVV } from "./internal/codegen"; +import { MATH2 } from "./internal/templates"; + +export const [submS2, submS3, submS4] = defOpS( + MATH2("-", "*"), + `${ARGS_VVV},${SARGS_VVV}`, + ARGS_VVV +); diff --git a/packages/vectors/src/subns.ts b/packages/vectors/src/subns.ts new file mode 100644 index 0000000000..4ad14fc3fc --- /dev/null +++ b/packages/vectors/src/subns.ts @@ -0,0 +1,14 @@ +import { VecOpSVN } from "./api"; +import { + ARGS_V, + ARGS_VN, + defOpS, + SARGS_V +} from "./internal/codegen"; +import { MATH_N } from "./internal/templates"; + +export const [subNS2, subNS3, subNS4] = defOpS( + MATH_N("-"), + `${ARGS_VN},${SARGS_V}`, + ARGS_V +); diff --git a/packages/vectors/src/vec2.ts b/packages/vectors/src/vec2.ts index a9a7f01097..f29a052ff3 100644 --- a/packages/vectors/src/vec2.ts +++ b/packages/vectors/src/vec2.ts @@ -11,16 +11,12 @@ import { Y2, ZERO2 } from "./api"; +import { intoStridedBuffer, mapStridedBuffer } from "./buffer"; import { eqDelta2 } from "./eqdelta"; import { hash } from "./hash"; import { declareIndices } from "./internal/accessors"; import { AVec } from "./internal/avec"; -import { - intoBuffer, - mapBuffer, - values, - vecIterator -} from "./internal/vec-utils"; +import { stridedValues, vecIterator } from "./internal/vec-utils"; import { setS2 } from "./sets"; export class Vec2 extends AVec implements IHash, IVector { @@ -45,7 +41,7 @@ export class Vec2 extends AVec implements IHash, IVector { cstride = 1, estride = 2 ) { - return mapBuffer(Vec2, buf, num, start, cstride, estride); + return mapStridedBuffer(Vec2, buf, num, start, cstride, estride); } /** @@ -69,7 +65,7 @@ export class Vec2 extends AVec implements IHash, IVector { cstride = 1, estride = 2 ) { - return intoBuffer(setS2, buf, src, start, cstride, estride); + return intoStridedBuffer(setS2, buf, src, start, cstride, estride); } static iterator( @@ -98,7 +94,7 @@ export class Vec2 extends AVec implements IHash, IVector { } [Symbol.iterator]() { - return values(this.buf, 2, this.offset, this.stride); + return stridedValues(this.buf, 2, this.offset, this.stride); } get length() { diff --git a/packages/vectors/src/vec3.ts b/packages/vectors/src/vec3.ts index e379eeea7a..bb16d48e25 100644 --- a/packages/vectors/src/vec3.ts +++ b/packages/vectors/src/vec3.ts @@ -12,16 +12,12 @@ import { Z3, ZERO3 } from "./api"; +import { intoStridedBuffer, mapStridedBuffer } from "./buffer"; import { eqDelta3 } from "./eqdelta"; import { hash } from "./hash"; import { declareIndices } from "./internal/accessors"; import { AVec } from "./internal/avec"; -import { - intoBuffer, - mapBuffer, - values, - vecIterator -} from "./internal/vec-utils"; +import { stridedValues, vecIterator } from "./internal/vec-utils"; import { setS3 } from "./sets"; export class Vec3 extends AVec implements IHash, IVector { @@ -46,7 +42,7 @@ export class Vec3 extends AVec implements IHash, IVector { cstride = 1, estride = 3 ) { - return mapBuffer(Vec3, buf, num, start, cstride, estride); + return mapStridedBuffer(Vec3, buf, num, start, cstride, estride); } /** @@ -70,7 +66,7 @@ export class Vec3 extends AVec implements IHash, IVector { cstride = 1, estride = 3 ) { - return intoBuffer(setS3, buf, src, start, cstride, estride); + return intoStridedBuffer(setS3, buf, src, start, cstride, estride); } static iterator( @@ -101,7 +97,7 @@ export class Vec3 extends AVec implements IHash, IVector { } [Symbol.iterator]() { - return values(this.buf, 3, this.offset, this.stride); + return stridedValues(this.buf, 3, this.offset, this.stride); } get length() { diff --git a/packages/vectors/src/vec4.ts b/packages/vectors/src/vec4.ts index 37b26f6fd5..c090c20ce6 100644 --- a/packages/vectors/src/vec4.ts +++ b/packages/vectors/src/vec4.ts @@ -12,16 +12,12 @@ import { Z4, ZERO4 } from "./api"; +import { intoStridedBuffer, mapStridedBuffer } from "./buffer"; import { eqDelta4 } from "./eqdelta"; import { hash } from "./hash"; import { declareIndices } from "./internal/accessors"; import { AVec } from "./internal/avec"; -import { - intoBuffer, - mapBuffer, - values, - vecIterator -} from "./internal/vec-utils"; +import { stridedValues, vecIterator } from "./internal/vec-utils"; import { setS4 } from "./sets"; export class Vec4 extends AVec implements IHash, IVector { @@ -46,7 +42,7 @@ export class Vec4 extends AVec implements IHash, IVector { cstride = 1, estride = 4 ) { - return mapBuffer(Vec4, buf, num, start, cstride, estride); + return mapStridedBuffer(Vec4, buf, num, start, cstride, estride); } /** @@ -70,7 +66,7 @@ export class Vec4 extends AVec implements IHash, IVector { cstride = 1, estride = 4 ) { - return intoBuffer(setS4, buf, src, start, cstride, estride); + return intoStridedBuffer(setS4, buf, src, start, cstride, estride); } static *iterator( @@ -102,7 +98,7 @@ export class Vec4 extends AVec implements IHash, IVector { } [Symbol.iterator]() { - return values(this.buf, 4, this.offset, this.stride); + return stridedValues(this.buf, 4, this.offset, this.stride); } get length() { diff --git a/packages/webgl-msdf/CHANGELOG.md b/packages/webgl-msdf/CHANGELOG.md index e2ab659c07..3d861b45b5 100644 --- a/packages/webgl-msdf/CHANGELOG.md +++ b/packages/webgl-msdf/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-msdf@0.1.8...@thi.ng/webgl-msdf@0.1.9) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/webgl-msdf + + + + + +## [0.1.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-msdf@0.1.7...@thi.ng/webgl-msdf@0.1.8) (2019-09-21) + +**Note:** Version bump only for package @thi.ng/webgl-msdf + + + + + +## [0.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-msdf@0.1.6...@thi.ng/webgl-msdf@0.1.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/webgl-msdf + + + + + +## [0.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-msdf@0.1.5...@thi.ng/webgl-msdf@0.1.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/webgl-msdf + + + + + +## [0.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-msdf@0.1.4...@thi.ng/webgl-msdf@0.1.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/webgl-msdf + + + + + +## [0.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-msdf@0.1.3...@thi.ng/webgl-msdf@0.1.4) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/webgl-msdf + + + + + ## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-msdf@0.1.2...@thi.ng/webgl-msdf@0.1.3) (2019-07-31) **Note:** Version bump only for package @thi.ng/webgl-msdf diff --git a/packages/webgl-msdf/package.json b/packages/webgl-msdf/package.json index 8a0101e2e6..5ba0e4c412 100644 --- a/packages/webgl-msdf/package.json +++ b/packages/webgl-msdf/package.json @@ -1,7 +1,7 @@ { "name": "@thi.ng/webgl-msdf", - "version": "0.1.3", - "description": "TODO", + "version": "0.1.9", + "description": "Multi-channel SDF font rendering & basic text layout for WebGL", "module": "./index.js", "main": "./lib/index.js", "umd:main": "./lib/index.umd.js", @@ -29,20 +29,21 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/shader-ast": "^0.2.1", - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/vector-pools": "^1.0.3", - "@thi.ng/vectors": "^3.0.3", - "@thi.ng/webgl": "^0.1.3" + "@thi.ng/api": "^6.5.0", + "@thi.ng/shader-ast": "^0.3.2", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vector-pools": "^1.0.9", + "@thi.ng/vectors": "^4.0.0", + "@thi.ng/webgl": "^0.2.1" }, "keywords": [ "ES6", "font", + "layout", "MSDF", "shader", "text", diff --git a/packages/webgl-msdf/src/shader.ts b/packages/webgl-msdf/src/shader.ts index 5ef47ed058..22b22b21f2 100644 --- a/packages/webgl-msdf/src/shader.ts +++ b/packages/webgl-msdf/src/shader.ts @@ -1,6 +1,6 @@ import { - $, $x, + $xyz, $y, $z, add, @@ -30,7 +30,7 @@ import { vec4 } from "@thi.ng/shader-ast"; import { ONE4, ZERO4 } from "@thi.ng/vectors"; -import { DEFAULT_BLEND, GLVec4, ShaderSpec } from "@thi.ng/webgl"; +import { BLEND_NORMAL, GLVec4, ShaderSpec } from "@thi.ng/webgl"; export interface MSDFShaderOpts { color: boolean; @@ -48,7 +48,7 @@ export const msdfSample = defn( let sd: FloatSym; let w: FloatSym; return [ - (sd = sym(sub(median3($(texture(tex, uv), "xyz")), FLOAT05))), + (sd = sym(sub(median3($xyz(texture(tex, uv))), FLOAT05))), (w = sym(clamp(add(div(sd, fwidth(sd)), FLOAT05), FLOAT0, FLOAT1))), ret(vec2(sd, w)) ]; @@ -101,6 +101,6 @@ export const msdfShader = (opts: Partial = {}): ShaderSpec => ({ }, state: { blend: true, - blendFn: DEFAULT_BLEND + blendFn: BLEND_NORMAL } }); diff --git a/packages/webgl-msdf/src/text.ts b/packages/webgl-msdf/src/text.ts index a19d532714..b70d13399b 100644 --- a/packages/webgl-msdf/src/text.ts +++ b/packages/webgl-msdf/src/text.ts @@ -1,3 +1,4 @@ +import { GLType } from "@thi.ng/api"; import { add, map, @@ -5,7 +6,7 @@ import { range, transduce } from "@thi.ng/transducers"; -import { AttribPool, GLType } from "@thi.ng/vector-pools"; +import { AttribPool } from "@thi.ng/vector-pools"; import { add2, div2, diff --git a/packages/webgl-shadertoy/.npmignore b/packages/webgl-shadertoy/.npmignore new file mode 100644 index 0000000000..74ea62d1fa --- /dev/null +++ b/packages/webgl-shadertoy/.npmignore @@ -0,0 +1,12 @@ +.meta +.nyc_output +*.html +*.tgz +build +coverage +dev +doc +export +src* +test +tsconfig.json diff --git a/packages/webgl-shadertoy/CHANGELOG.md b/packages/webgl-shadertoy/CHANGELOG.md new file mode 100644 index 0000000000..f4baf413a5 --- /dev/null +++ b/packages/webgl-shadertoy/CHANGELOG.md @@ -0,0 +1,30 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl-shadertoy@0.1.0...@thi.ng/webgl-shadertoy@0.1.1) (2019-11-09) + +**Note:** Version bump only for package @thi.ng/webgl-shadertoy + + + + + +# 0.1.0 (2019-09-21) + + +### Bug Fixes + +* **webgl-shadertoy:** update imports ([7d6ed77](https://github.com/thi-ng/umbrella/commit/7d6ed77)) +* **webgl-shadertoy:** update texture/sampler & FBO handling ([25845e5](https://github.com/thi-ng/umbrella/commit/25845e5)) + + +### Features + +* **webgl-shadertoy:** add optional per-pass ModelSpec & vert shader support ([a45725a](https://github.com/thi-ng/umbrella/commit/a45725a)) +* **webgl-shadertoy:** fix & update drawPass viewport, add update() method ([5d2c17e](https://github.com/thi-ng/umbrella/commit/5d2c17e)) +* **webgl-shadertoy:** import new pkg ([35d9b68](https://github.com/thi-ng/umbrella/commit/35d9b68)) +* **webgl-shadertoy:** initial multipass skeleton ([c287dab](https://github.com/thi-ng/umbrella/commit/c287dab)) +* **webgl-shadertoy:** simplify mainImage user fn handling, update types & readme ([bd1b88e](https://github.com/thi-ng/umbrella/commit/bd1b88e)) +* **webgl-shadertoy:** update multipass uniform handling ([2071133](https://github.com/thi-ng/umbrella/commit/2071133)) diff --git a/packages/webgl-shadertoy/LICENSE b/packages/webgl-shadertoy/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/webgl-shadertoy/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/webgl-shadertoy/README.md b/packages/webgl-shadertoy/README.md new file mode 100644 index 0000000000..dd31aff7e7 --- /dev/null +++ b/packages/webgl-shadertoy/README.md @@ -0,0 +1,117 @@ +# @thi.ng/webgl-shadertoy + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/webgl-shadertoy.svg)](https://www.npmjs.com/package/@thi.ng/webgl-shadertoy) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/webgl-shadertoy.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + +- [About](#about) +- [Status](#status) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) +- [Authors](#authors) +- [License](#license) + + + +## About + +Basic WebGL scaffolding for running interactive fragment shaders defined +via +[@thi.ng/shader-ast](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast). + +## Status + +ALPHA - multi-pass support forthcoming + +## Installation + +```bash +yarn add @thi.ng/webgl-shadertoy +``` + +## Dependencies + +- [@thi.ng/shader-ast](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast) +- [@thi.ng/webgl](https://github.com/thi-ng/umbrella/tree/master/packages/webgl) + +## Usage examples + +[Live version](https://demo.thi.ng/umbrella/webgl-shadertoy/) + +```ts +import { + $xy, add, distance, eq, float, FloatSym, fract, + int, min, mix, mul, neg, ret, sin, sym, + Vec2Sym, Vec2Term, vec3, Vec3Sym, vec4 +} from "@thi.ng/shader-ast"; +import { aspectCorrectedUV, fit1101 } from "@thi.ng/shader-ast-stdlib"; +import { glCanvas } from "@thi.ng/webgl"; +import { MainImageFn, shaderToy } from "@thi.ng/webgl-shadertoy"; + +const mainImage: MainImageFn = (gl, unis) => { + // predeclare local vars / symbols + let uv: Vec2Sym; + let mp: Vec2Sym; + let d1: FloatSym; + let d2: FloatSym; + let col: Vec3Sym; + + // Inline function to create ring pattern with center at `p` + const rings = (p: Vec2Term, speed = 0.25, freq = 50) => + sin(mul(add(distance(uv, p), fract(mul(unis.time, speed))), freq)); + + return [ + // let's work in [-1..+1] range (based on vertical resolution) + (uv = sym(aspectCorrectedUV($xy(gl.gl_FragCoord), unis.resolution))), + (mp = sym(aspectCorrectedUV(unis.mouse, unis.resolution))), + // compute ring colors + (d1 = sym(rings(mp))), + (d2 = sym(rings(neg(mp)))), + // combine rings and multiply with target color based on + // mouse button state + (col = sym( + mul( + vec3(fit1101(min(d1, d2))), + mix( + vec3(1), + vec3(d1, 0, d2), + float(eq(unis.mouseButtons, int(1))) + ) + ) + )), + // return as vec4 (mandatory) + ret(vec4(col, 1)) + ]; +}; + +// create WebGL canvas +const canvas = glCanvas({ + width: window.innerWidth, + height: window.innerHeight, + parent: document.body, + version: 1 +}); + +// init shader toy with canvas & shader fn +const toy = shaderToy({ + canvas: canvas.canvas, + gl: canvas.gl, + main: mainImage +}); + +toy.start(); +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/webgl-shadertoy/package.json b/packages/webgl-shadertoy/package.json new file mode 100644 index 0000000000..23dc7ee7fe --- /dev/null +++ b/packages/webgl-shadertoy/package.json @@ -0,0 +1,56 @@ +{ + "name": "@thi.ng/webgl-shadertoy", + "version": "0.1.1", + "description": "Basic WebGL scaffolding for running interactive fragment shaders", + "module": "./index.js", + "main": "./lib/index.js", + "umd:main": "./lib/index.umd.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/webgl", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module", + "build:release": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module all", + "build:es6": "tsc --declaration", + "build:test": "rimraf build && tsc -p test/tsconfig.json", + "test": "yarn build:test && mocha build/test/*.js", + "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn build && yarn publish --access public" + }, + "devDependencies": { + "@types/mocha": "^5.2.6", + "@types/node": "^12.6.3", + "mocha": "^6.1.4", + "nyc": "^14.0.0", + "typedoc": "^0.15.0", + "typescript": "^3.6.4" + }, + "dependencies": { + "@thi.ng/api": "^6.5.0", + "@thi.ng/shader-ast": "^0.3.2", + "@thi.ng/shader-ast-glsl": "^0.1.8", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/webgl": "^0.2.1" + }, + "keywords": [ + "ES6", + "graphics", + "shader-ast", + "shadertoy", + "texture", + "typescript", + "webgl", + "webgl2" + ], + "publishConfig": { + "access": "public" + }, + "sideEffects": false +} diff --git a/packages/webgl-shadertoy/src/api.ts b/packages/webgl-shadertoy/src/api.ts new file mode 100644 index 0000000000..3a151d5e00 --- /dev/null +++ b/packages/webgl-shadertoy/src/api.ts @@ -0,0 +1,43 @@ +import { Fn2 } from "@thi.ng/api"; +import { + FloatSym, + IntSym, + ScopeBody, + Vec2Sym +} from "@thi.ng/shader-ast"; +import { GLSLTarget } from "@thi.ng/shader-ast-glsl"; +import { ITexture, ModelSpec, ShaderUniformSpecs } from "@thi.ng/webgl"; + +export type MainImageFn = Fn2; + +export interface ShaderToyUniforms { + resolution: Vec2Sym; + mouse: Vec2Sym; + mouseButtons: IntSym; + time: FloatSym; +} + +export interface ShaderToyOpts { + canvas: HTMLCanvasElement; + gl: WebGLRenderingContext; + /** + * Main user shader function + */ + main: MainImageFn; + /** + * Optional additional uniforms + */ + uniforms?: ShaderUniformSpecs; + /** + * Optional textures to bind + */ + textures?: ITexture[]; +} + +export interface ShaderToy { + start(): void; + stop(): void; + update(time?: number): void; + recompile(main: MainImageFn): void; + model: ModelSpec; +} diff --git a/packages/webgl-shadertoy/src/index.ts b/packages/webgl-shadertoy/src/index.ts new file mode 100644 index 0000000000..cd686c7701 --- /dev/null +++ b/packages/webgl-shadertoy/src/index.ts @@ -0,0 +1,2 @@ +export * from "./api"; +export * from "./shadertoy"; diff --git a/packages/webgl-shadertoy/src/shadertoy.ts b/packages/webgl-shadertoy/src/shadertoy.ts new file mode 100644 index 0000000000..beaa24d27e --- /dev/null +++ b/packages/webgl-shadertoy/src/shadertoy.ts @@ -0,0 +1,108 @@ +import { + assign, + defMain, + defn, + FLOAT0, + FLOAT1, + vec4 +} from "@thi.ng/shader-ast"; +import { + compileModel, + draw, + quad, + shader +} from "@thi.ng/webgl"; +import { MainImageFn, ShaderToy, ShaderToyOpts } from "./api"; + +export const shaderToy = (opts: ShaderToyOpts) => { + const gl = opts.gl; + + const model = quad(false); + model.textures = opts.textures || []; + compileModel(gl, model); + + opts.canvas.addEventListener("mousemove", (e) => { + const rect = opts.canvas.getBoundingClientRect(); + const dpr = window.devicePixelRatio; + model.uniforms!.mouse = [ + (e.clientX - rect.left) * dpr, + (rect.height - (e.clientY - rect.top)) * dpr + ]; + }); + opts.canvas.addEventListener("mousedown", (e) => { + model.uniforms!.mouseButtons = e.buttons; + }); + opts.canvas.addEventListener("mouseup", (e) => { + model.uniforms!.mouseButtons = e.buttons; + }); + + let active: boolean; + let t0: number; + + const update = (time: number) => { + const w = gl.drawingBufferWidth; + const h = gl.drawingBufferHeight; + model.uniforms!.time = time; + model.uniforms!.resolution = [w, h]; + + gl.viewport(0, 0, w, h); + draw(model); + }; + + const updateRAF = () => { + update((Date.now() - t0) * 1e-3); + active && requestAnimationFrame(updateRAF); + }; + + const instance: ShaderToy = { + start() { + t0 = Date.now(); + active = true; + requestAnimationFrame(updateRAF); + }, + stop() { + active = false; + }, + update(time: number) { + update(time); + }, + recompile(main: MainImageFn) { + if (model.shader) { + model.shader.release(); + } + model.shader = shader(gl, { + vs: (gl, _, ins) => [ + defMain(() => [ + assign( + gl.gl_Position, + vec4(ins.position, FLOAT0, FLOAT1) + ) + ]) + ], + fs: (gl, unis, _, outputs) => [ + defMain(() => [ + assign( + outputs.fragColor, + defn("vec4", "mainImage", [], () => + main(gl, unis) + )() + ) + ]) + ], + attribs: { + position: "vec2" + }, + uniforms: { + resolution: "vec2", + mouse: ["vec2", [0, 0]], + mouseButtons: ["int", 0], + time: "float", + ...opts.uniforms + } + }); + }, + model + }; + instance.recompile(opts.main); + return instance; +}; diff --git a/packages/webgl-shadertoy/test/index.ts b/packages/webgl-shadertoy/test/index.ts new file mode 100644 index 0000000000..294a59348c --- /dev/null +++ b/packages/webgl-shadertoy/test/index.ts @@ -0,0 +1,6 @@ +// import * as assert from "assert"; +// import * as ws from "../src/index"; + +describe("webgl-shadertoy", () => { + it("tests pending"); +}); diff --git a/packages/webgl-shadertoy/test/tsconfig.json b/packages/webgl-shadertoy/test/tsconfig.json new file mode 100644 index 0000000000..f6e63560dd --- /dev/null +++ b/packages/webgl-shadertoy/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build", + "module": "commonjs" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/webgl-shadertoy/tsconfig.json b/packages/webgl-shadertoy/tsconfig.json new file mode 100644 index 0000000000..893b9979c5 --- /dev/null +++ b/packages/webgl-shadertoy/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "module": "es6", + "target": "es6" + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/webgl/CHANGELOG.md b/packages/webgl/CHANGELOG.md index e25581ee20..d7c7644df6 100644 --- a/packages/webgl/CHANGELOG.md +++ b/packages/webgl/CHANGELOG.md @@ -3,6 +3,74 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl@0.2.0...@thi.ng/webgl@0.2.1) (2019-11-09) + + +### Bug Fixes + +* **webgl:** add LOGGER, update initUniforms() ([4719110](https://github.com/thi-ng/umbrella/commit/471911084c8db79930cf273f222f345318671953)) +* **webgl:** ensure system defaults for all uniforms, update equiv checks ([39dc83f](https://github.com/thi-ng/umbrella/commit/39dc83ff49c97fb7ba70f7bbf0f7244d612c7fc8)) + + + + + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl@0.1.7...@thi.ng/webgl@0.2.0) (2019-09-21) + + +### Bug Fixes + +* **webgl:** update extension handling in shader(), add ExtensionInfo ([12abaa0](https://github.com/thi-ng/umbrella/commit/12abaa0)) +* **webgl:** update samplerXX[] uniform decl types ([48b8906](https://github.com/thi-ng/umbrella/commit/48b8906)) + + +### Features + +* **webgl:** add blending & stencil enums/types ([c8898a0](https://github.com/thi-ng/umbrella/commit/c8898a0)) +* **webgl:** add initial coll of blend mode presets ([58e0b04](https://github.com/thi-ng/umbrella/commit/58e0b04)) +* **webgl:** add readPixels/readTexture(), add ReadableTextureFormat ([355f785](https://github.com/thi-ng/umbrella/commit/355f785)) +* **webgl:** add renderExt to TextureFormatDecl, add FBO tex fmt checks ([180e89c](https://github.com/thi-ng/umbrella/commit/180e89c)) +* **webgl:** migrate multipass() & types from webgl-shadertoy pkg, reorg ([2aa31ce](https://github.com/thi-ng/umbrella/commit/2aa31ce)) +* **webgl:** update multipass / PassOpts ([95aba16](https://github.com/thi-ng/umbrella/commit/95aba16)) +* **webgl:** update texture config, split api.ts into mult files ([052552f](https://github.com/thi-ng/umbrella/commit/052552f)) +* **webgl:** update Texture.configure, store target, format, type, size ([9131310](https://github.com/thi-ng/umbrella/commit/9131310)) + + + + + +## [0.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl@0.1.6...@thi.ng/webgl@0.1.7) (2019-08-21) + +**Note:** Version bump only for package @thi.ng/webgl + + + + + +## [0.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl@0.1.5...@thi.ng/webgl@0.1.6) (2019-08-17) + +**Note:** Version bump only for package @thi.ng/webgl + + + + + +## [0.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl@0.1.4...@thi.ng/webgl@0.1.5) (2019-08-16) + +**Note:** Version bump only for package @thi.ng/webgl + + + + + +## [0.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl@0.1.3...@thi.ng/webgl@0.1.4) (2019-07-31) + +**Note:** Version bump only for package @thi.ng/webgl + + + + + ## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/webgl@0.1.2...@thi.ng/webgl@0.1.3) (2019-07-31) diff --git a/packages/webgl/README.md b/packages/webgl/README.md index 2f181ba81c..48cbdf60a4 100644 --- a/packages/webgl/README.md +++ b/packages/webgl/README.md @@ -41,12 +41,13 @@ updated from Clojure/ClojureScript versions of - customizable shader presets - Declarative geometry, attribute & index buffer specs - Declarative instancing (always available in WebGL2, or via ANGLE ext in WebGL1) - - also supported by bundled shader presets -- Texture wrapper & declarative config +- Texture wrapper, declarative config +- Comprehensive texture format info (channels, strides, renderable, filterable etc.) - FBO support with multiple attachments & render buffers -- GPGPU job utilities +- Multi-pass shader pipeline (e.g. for GPGPU tasks) +- Pixel reading from main color buffer and textures - Geometry & texture generators -- WebGL extension helpers +- WebGL extension helpers & semi-automatic extension enabling - WebGL canvas creation / setup Status: Alpha / WIP @@ -76,6 +77,7 @@ yarn add @thi.ng/webgl - [@thi.ng/shader-ast-stdlib](https://github.com/thi-ng/umbrella/tree/master/packages/shader-ast-stdlib) (also see readme for reference) - [@thi.ng/webgl-msdf](https://github.com/thi-ng/umbrella/tree/master/packages/webgl-msdf) +- [@thi.ng/webgl-shadertoy](https://github.com/thi-ng/umbrella/tree/master/packages/webgl-shadertoy) ## Usage examples @@ -88,8 +90,9 @@ folder of this repo... - [Textured tunnel](https://github.com/thi-ng/umbrella/tree/master/examples/shader-ast-tunnel) - [Cubemap](https://github.com/thi-ng/umbrella/tree/master/examples/webgl-cubemap) - [Grid instancing](https://github.com/thi-ng/umbrella/tree/master/examples/webgl-grid) -- [GPGPU basics](https://github.com/thi-ng/umbrella/tree/master/examples/webgl-gpgpu-basics) +- [Multipass / GPGPU](https://github.com/thi-ng/umbrella/tree/master/examples/webgl-multipass) - [MSDF font rendering](https://github.com/thi-ng/umbrella/tree/master/examples/webgl-msdf) +- [Minimal shadertoy](https://github.com/thi-ng/umbrella/tree/master/examples/webgl-shadertoy) - [SSAO deferred rendering](https://github.com/thi-ng/umbrella/tree/master/examples/webgl-ssao) ## Authors diff --git a/packages/webgl/package.json b/packages/webgl/package.json index bc4fc6dace..3b38c2ee9f 100644 --- a/packages/webgl/package.json +++ b/packages/webgl/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/webgl", - "version": "0.1.3", + "version": "0.2.1", "description": "WebGL abstraction layer", "module": "./index.js", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "build:test": "rimraf build && tsc -p test/tsconfig.json", "test": "yarn build:test && mocha build/test/*.js", "cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov", - "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib geo shaders textures", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib api geo shaders textures", "doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src", "pub": "yarn build:release && yarn publish --access public" }, @@ -29,24 +29,24 @@ "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.2", - "@thi.ng/associative": "^2.4.2", - "@thi.ng/binary": "^1.1.0", - "@thi.ng/checks": "^2.2.2", - "@thi.ng/equiv": "^1.0.9", - "@thi.ng/errors": "^1.1.2", - "@thi.ng/matrices": "^0.5.3", - "@thi.ng/pixel": "^0.1.0", - "@thi.ng/shader-ast": "^0.2.1", - "@thi.ng/shader-ast-glsl": "^0.1.3", - "@thi.ng/shader-ast-stdlib": "^0.2.0", - "@thi.ng/transducers": "^5.4.2", - "@thi.ng/vector-pools": "^1.0.3", - "@thi.ng/vectors": "^3.0.3" + "@thi.ng/api": "^6.5.0", + "@thi.ng/associative": "^3.1.0", + "@thi.ng/binary": "^1.1.1", + "@thi.ng/checks": "^2.4.1", + "@thi.ng/equiv": "^1.0.10", + "@thi.ng/errors": "^1.2.1", + "@thi.ng/matrices": "^0.5.9", + "@thi.ng/pixel": "^0.1.5", + "@thi.ng/shader-ast": "^0.3.2", + "@thi.ng/shader-ast-glsl": "^0.1.8", + "@thi.ng/shader-ast-stdlib": "^0.3.1", + "@thi.ng/transducers": "^6.0.0", + "@thi.ng/vector-pools": "^1.0.9", + "@thi.ng/vectors": "^4.0.0" }, "keywords": [ "declarative", diff --git a/packages/webgl/src/api.ts b/packages/webgl/src/api.ts deleted file mode 100644 index 57de6c98b7..0000000000 --- a/packages/webgl/src/api.ts +++ /dev/null @@ -1,1153 +0,0 @@ -import { - Fn, - Fn2, - Fn3, - Fn4, - IBind, - IDeref, - IObjectOf, - IRelease, - Tuple, - TypedArray -} from "@thi.ng/api"; -import { Func, Sym, Type } from "@thi.ng/shader-ast"; -import { GLSLTarget } from "@thi.ng/shader-ast-glsl"; -import { AttribPool } from "@thi.ng/vector-pools"; -import { ReadonlyVec } from "@thi.ng/vectors"; - -export const enum TextureFormat { - ALPHA = 0x1906, - DEPTH_COMPONENT = 0x1902, - DEPTH_COMPONENT16 = 0x81a5, - DEPTH_COMPONENT24 = 0x81a6, - DEPTH_COMPONENT32F = 0x8cac, - DEPTH_STENCIL = 0x84f9, - DEPTH24_STENCIL8 = 0x88f0, - DEPTH32F_STENCIL8 = 0x8cad, - LUMINANCE = 0x1909, - LUMINANCE_ALPHA = 0x190a, - R11F_G11F_B10F = 0x8c3a, - R16F = 0x822d, - R16I = 0x8233, - R16UI = 0x8234, - R32F = 0x822e, - R32I = 0x8235, - R32UI = 0x8236, - R8 = 0x8229, - R8_SNORM = 0x8f94, - R8I = 0x8231, - R8UI = 0x8232, - RED = 0x1903, - RED_INTEGER = 0x8d94, - RG = 0x8227, - RG_INTEGER = 0x8228, - RG16F = 0x822f, - RG16I = 0x8239, - RG16UI = 0x823a, - RG32F = 0x8230, - RG32I = 0x823b, - RG32UI = 0x823c, - RG8 = 0x822b, - RG8_SNORM = 0x8f95, - RG8I = 0x8237, - RG8UI = 0x8238, - RGB = 0x1907, - RGB_INTEGER = 0x8d98, - RGB10_A2 = 0x8059, - RGB10_A2UI = 0x906f, - RGB16F = 0x881b, - RGB16I = 0x8d89, - RGB16UI = 0x8d77, - RGB32F = 0x8815, - RGB32I = 0x8d83, - RGB32UI = 0x8d71, - RGB5_A1 = 0x8057, - RGB565 = 0x8d62, - RGB8 = 0x8051, - RGB8_SNORM = 0x8f96, - RGB8I = 0x8d8f, - RGB8UI = 0x8d7d, - RGB9_E5 = 0x8c3d, - RGBA = 0x1908, - RGBA_INTEGER = 0x8d99, - RGBA16F = 0x881a, - RGBA16I = 0x8d88, - RGBA16UI = 0x8d76, - RGBA32F = 0x8814, - RGBA32I = 0x8d82, - RGBA32UI = 0x8d70, - RGBA4 = 0x8056, - RGBA8 = 0x8058, - RGBA8_SNORM = 0x8f97, - RGBA8I = 0x8d8e, - RGBA8UI = 0x8d7c, - SRGB8 = 0x8c41, - SRGB8_ALPHA8 = 0x8c43 -} - -export const enum TextureType { - BYTE = 0x1400, - UNSIGNED_BYTE, - SHORT, - UNSIGNED_SHORT, - INT, - UNSIGNED_INT, - FLOAT, - HALF_FLOAT = 0x140b, - UNSIGNED_SHORT_4_4_4_4 = 0x8033, - UNSIGNED_SHORT_5_5_5_1 = 0x8034, - UNSIGNED_SHORT_5_6_5 = 0x8363, - UNSIGNED_INT_2_10_10_10_REV = 0x8368, - UNSIGNED_INT_24_8 = 0x84fa, - UNSIGNED_INT_10F_11F_11F_REV = 0x8c3b, - UNSIGNED_INT_5_9_9_9_REV = 0x8c3e, - HALF_FLOAT_OES = 0x8d61, - FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8dad -} - -export interface TextureFormatDecl { - /** - * Base format - */ - format: TextureFormat; - /** - * Acceptable types and their byte sizes - */ - types: { [id: number]: number }; - /** - * Number of color components - */ - num: number; - /** - * Format is renderable - */ - render?: boolean; - /** - * Format is filterable (other than GL_NEAREST) - */ - filter?: boolean; - /** - * WebGL 2 only - */ - gl2?: boolean; -} - -export const TEX_FORMATS: IObjectOf = { - [TextureFormat.ALPHA]: { - format: TextureFormat.ALPHA, - render: true, - filter: true, - num: 1, - types: { - [TextureType.UNSIGNED_BYTE]: 1, - [TextureType.HALF_FLOAT]: 2, - [TextureType.HALF_FLOAT_OES]: 2, - [TextureType.FLOAT]: 4 - } - }, - [TextureFormat.DEPTH_COMPONENT16]: { - format: TextureFormat.DEPTH_COMPONENT, - render: true, - num: 1, - types: { - [TextureType.UNSIGNED_SHORT]: 2, - [TextureType.UNSIGNED_INT]: 4 - } - }, - [TextureFormat.DEPTH_COMPONENT24]: { - format: TextureFormat.DEPTH_COMPONENT, - render: true, - num: 1, - types: { [TextureType.UNSIGNED_INT]: 4 } - }, - [TextureFormat.DEPTH_COMPONENT32F]: { - format: TextureFormat.DEPTH_COMPONENT, - render: true, - num: 1, - types: { [TextureType.FLOAT]: 4 } - }, - [TextureFormat.DEPTH24_STENCIL8]: { - format: TextureFormat.DEPTH_STENCIL, - render: true, - num: 1, - types: { [TextureType.UNSIGNED_INT_24_8]: 4 } - }, - [TextureFormat.DEPTH32F_STENCIL8]: { - format: TextureFormat.DEPTH_STENCIL, - render: true, - num: 1, - types: { [TextureType.FLOAT_32_UNSIGNED_INT_24_8_REV]: 4 } - }, - [TextureFormat.LUMINANCE_ALPHA]: { - format: TextureFormat.LUMINANCE_ALPHA, - render: true, - filter: true, - num: 2, - types: { - [TextureType.UNSIGNED_BYTE]: 2, - [TextureType.HALF_FLOAT]: 4, - [TextureType.HALF_FLOAT_OES]: 4, - [TextureType.FLOAT]: 8 - } - }, - [TextureFormat.LUMINANCE]: { - format: TextureFormat.LUMINANCE, - render: true, - filter: true, - num: 1, - types: { - [TextureType.UNSIGNED_BYTE]: 1, - [TextureType.HALF_FLOAT]: 2, - [TextureType.HALF_FLOAT_OES]: 2, - [TextureType.FLOAT]: 4 - } - }, - [TextureFormat.R11F_G11F_B10F]: { - format: TextureFormat.RGB, - filter: true, - num: 3, - types: { - [TextureType.FLOAT]: 12, - [TextureType.HALF_FLOAT]: 6, - [TextureType.UNSIGNED_INT_10F_11F_11F_REV]: 4 - } - }, - [TextureFormat.R16F]: { - format: TextureFormat.RED, - filter: true, - num: 1, - types: { [TextureType.FLOAT]: 4, [TextureType.HALF_FLOAT]: 2 } - }, - [TextureFormat.R16I]: { - format: TextureFormat.RED_INTEGER, - render: true, - num: 1, - types: { [TextureType.SHORT]: 2 } - }, - [TextureFormat.R16UI]: { - format: TextureFormat.RED_INTEGER, - render: true, - num: 1, - types: { [TextureType.UNSIGNED_SHORT]: 2 } - }, - [TextureFormat.R32F]: { - format: TextureFormat.RED, - num: 1, - types: { [TextureType.FLOAT]: 4 } - }, - [TextureFormat.R32I]: { - format: TextureFormat.RED_INTEGER, - render: true, - num: 1, - types: { [TextureType.INT]: 4 } - }, - [TextureFormat.R32UI]: { - format: TextureFormat.RED_INTEGER, - render: true, - num: 1, - types: { [TextureType.UNSIGNED_INT]: 4 } - }, - [TextureFormat.R8_SNORM]: { - format: TextureFormat.RED, - filter: true, - num: 1, - types: { [TextureType.BYTE]: 1 } - }, - [TextureFormat.R8]: { - format: TextureFormat.RED, - render: true, - filter: true, - num: 1, - types: { [TextureType.UNSIGNED_BYTE]: 1 } - }, - [TextureFormat.R8I]: { - format: TextureFormat.RED_INTEGER, - render: true, - num: 1, - types: { [TextureType.BYTE]: 1 } - }, - [TextureFormat.R8UI]: { - format: TextureFormat.RED_INTEGER, - render: true, - num: 1, - types: { [TextureType.UNSIGNED_BYTE]: 1 } - }, - [TextureFormat.RG16F]: { - format: TextureFormat.RG, - filter: true, - num: 2, - types: { [TextureType.FLOAT]: 8, [TextureType.HALF_FLOAT]: 4 } - }, - [TextureFormat.RG16I]: { - format: TextureFormat.RG_INTEGER, - render: true, - num: 2, - types: { [TextureType.SHORT]: 4 } - }, - [TextureFormat.RG16UI]: { - format: TextureFormat.RG_INTEGER, - render: true, - num: 2, - types: { [TextureType.UNSIGNED_SHORT]: 4 } - }, - [TextureFormat.RG32F]: { - format: TextureFormat.RG, - num: 2, - types: { [TextureType.FLOAT]: 8 } - }, - [TextureFormat.RG32I]: { - format: TextureFormat.RG_INTEGER, - render: true, - num: 2, - types: { [TextureType.INT]: 8 } - }, - [TextureFormat.RG32UI]: { - format: TextureFormat.RG_INTEGER, - render: true, - num: 2, - types: { [TextureType.UNSIGNED_INT]: 8 } - }, - [TextureFormat.RG8_SNORM]: { - format: TextureFormat.RG, - filter: true, - num: 2, - types: { [TextureType.BYTE]: 2 } - }, - [TextureFormat.RG8]: { - format: TextureFormat.RG, - render: true, - filter: true, - num: 2, - types: { [TextureType.UNSIGNED_BYTE]: 2 } - }, - [TextureFormat.RG8I]: { - format: TextureFormat.RG_INTEGER, - render: true, - num: 2, - types: { [TextureType.BYTE]: 2 } - }, - [TextureFormat.RG8UI]: { - format: TextureFormat.RG_INTEGER, - render: true, - num: 2, - types: { [TextureType.UNSIGNED_BYTE]: 2 } - }, - [TextureFormat.RGB]: { - format: TextureFormat.RGB, - render: true, - filter: true, - num: 3, - types: { - [TextureType.UNSIGNED_BYTE]: 3, - [TextureType.HALF_FLOAT]: 6, - [TextureType.HALF_FLOAT_OES]: 6, - [TextureType.FLOAT]: 12, - [TextureType.UNSIGNED_SHORT_5_6_5]: 2 - } - }, - [TextureFormat.RGB10_A2]: { - format: TextureFormat.RGBA, - render: true, - filter: true, - num: 4, - types: { [TextureType.UNSIGNED_INT_2_10_10_10_REV]: 4 } - }, - [TextureFormat.RGB10_A2UI]: { - format: TextureFormat.RGBA_INTEGER, - render: true, - num: 4, - types: { [TextureType.UNSIGNED_INT_2_10_10_10_REV]: 4 } - }, - [TextureFormat.RGB16F]: { - format: TextureFormat.RGB, - filter: true, - num: 3, - types: { [TextureType.FLOAT]: 12, [TextureType.HALF_FLOAT]: 6 } - }, - [TextureFormat.RGB16I]: { - format: TextureFormat.RGB_INTEGER, - num: 3, - types: { [TextureType.SHORT]: 6 } - }, - [TextureFormat.RGB16UI]: { - format: TextureFormat.RGB_INTEGER, - num: 3, - types: { [TextureType.UNSIGNED_SHORT]: 6 } - }, - [TextureFormat.RGB32F]: { - format: TextureFormat.RGB, - num: 3, - types: { [TextureType.FLOAT]: 12 } - }, - [TextureFormat.RGB32I]: { - format: TextureFormat.RGB_INTEGER, - num: 3, - types: { [TextureType.INT]: 12 } - }, - [TextureFormat.RGB32UI]: { - format: TextureFormat.RGB_INTEGER, - num: 3, - types: { [TextureType.UNSIGNED_INT]: 12 } - }, - [TextureFormat.RGB5_A1]: { - format: TextureFormat.RGBA, - render: true, - filter: true, - num: 4, - types: { - [TextureType.UNSIGNED_BYTE]: 4, - [TextureType.UNSIGNED_SHORT_5_5_5_1]: 2, - [TextureType.UNSIGNED_INT_2_10_10_10_REV]: 4 - } - }, - [TextureFormat.RGB565]: { - format: TextureFormat.RGB, - render: true, - filter: true, - num: 3, - types: { - [TextureType.UNSIGNED_BYTE]: 3, - [TextureType.UNSIGNED_SHORT_5_6_5]: 2 - } - }, - [TextureFormat.RGB8_SNORM]: { - format: TextureFormat.RGB, - filter: true, - num: 3, - types: { [TextureType.BYTE]: 3 } - }, - [TextureFormat.RGB8]: { - format: TextureFormat.RGB, - render: true, - filter: true, - num: 3, - types: { [TextureType.UNSIGNED_BYTE]: 3 } - }, - [TextureFormat.RGB8I]: { - format: TextureFormat.RGB_INTEGER, - num: 3, - types: { [TextureType.BYTE]: 3 } - }, - [TextureFormat.RGB8UI]: { - format: TextureFormat.RGB_INTEGER, - num: 3, - types: { [TextureType.UNSIGNED_BYTE]: 3 } - }, - [TextureFormat.RGB9_E5]: { - format: TextureFormat.RGB, - filter: true, - num: 3, - types: { - [TextureType.FLOAT]: 12, - [TextureType.HALF_FLOAT]: 6, - [TextureType.UNSIGNED_INT_5_9_9_9_REV]: 4 - } - }, - [TextureFormat.RGBA]: { - format: TextureFormat.RGBA, - render: true, - filter: true, - num: 4, - types: { - [TextureType.UNSIGNED_BYTE]: 4, - [TextureType.HALF_FLOAT]: 8, - [TextureType.HALF_FLOAT_OES]: 8, - [TextureType.FLOAT]: 16, - [TextureType.UNSIGNED_SHORT_4_4_4_4]: 2, - [TextureType.UNSIGNED_SHORT_5_5_5_1]: 2 - } - }, - [TextureFormat.RGBA16F]: { - format: TextureFormat.RGBA, - filter: true, - num: 4, - types: { [TextureType.FLOAT]: 16, [TextureType.HALF_FLOAT]: 8 } - }, - [TextureFormat.RGBA16I]: { - format: TextureFormat.RGBA_INTEGER, - render: true, - num: 4, - types: { [TextureType.SHORT]: 8 } - }, - [TextureFormat.RGBA16UI]: { - format: TextureFormat.RGBA_INTEGER, - render: true, - num: 4, - types: { [TextureType.UNSIGNED_SHORT]: 8 } - }, - [TextureFormat.RGBA32F]: { - format: TextureFormat.RGBA, - num: 4, - types: { [TextureType.FLOAT]: 16 } - }, - [TextureFormat.RGBA32I]: { - format: TextureFormat.RGBA_INTEGER, - render: true, - num: 4, - types: { [TextureType.INT]: 16 } - }, - [TextureFormat.RGBA32UI]: { - format: TextureFormat.RGBA_INTEGER, - render: true, - num: 4, - types: { [TextureType.UNSIGNED_INT]: 16 } - }, - [TextureFormat.RGBA4]: { - format: TextureFormat.RGBA, - render: true, - filter: true, - num: 4, - types: { - [TextureType.UNSIGNED_BYTE]: 4, - [TextureType.UNSIGNED_SHORT_4_4_4_4]: 2 - } - }, - [TextureFormat.RGBA8_SNORM]: { - format: TextureFormat.RGBA, - filter: true, - num: 4, - types: { [TextureType.BYTE]: 4 } - }, - [TextureFormat.RGBA8]: { - format: TextureFormat.RGBA, - render: true, - filter: true, - num: 4, - types: { [TextureType.UNSIGNED_BYTE]: 4 } - }, - [TextureFormat.RGBA8I]: { - format: TextureFormat.RGBA_INTEGER, - render: true, - num: 4, - types: { [TextureType.BYTE]: 4 } - }, - [TextureFormat.RGBA8UI]: { - format: TextureFormat.RGBA_INTEGER, - render: true, - num: 4, - types: { [TextureType.UNSIGNED_BYTE]: 4 } - }, - [TextureFormat.SRGB8_ALPHA8]: { - format: TextureFormat.RGBA, - render: true, - filter: true, - num: 4, - types: { [TextureType.UNSIGNED_BYTE]: 4 } - }, - [TextureFormat.SRGB8]: { - format: TextureFormat.RGB, - filter: true, - num: 3, - types: { - [TextureType.UNSIGNED_BYTE]: 3 - } - } -}; - -export type GLSL = Type; - -export type GLVec = number[] | Float32Array; -export type GLVec2 = Tuple | Float32Array; -export type GLVec3 = Tuple | Float32Array; -export type GLVec4 = Tuple | Float32Array; - -export type GLIntVec = number[] | Int32Array; -export type GLUintVec = number[] | Uint32Array; -export type GLIntVec2 = Tuple | Int32Array; -export type GLIntVec3 = Tuple | Int32Array; -export type GLIntVec4 = Tuple | Int32Array; - -export type GLMat2 = Tuple | Float32Array; -export type GLMat3 = Tuple | Float32Array; -export type GLMat4 = Tuple | Float32Array; -export type GLMat23 = Tuple | Float32Array; -export type GLMat24 = Tuple | Float32Array; -export type GLMat34 = Tuple | Float32Array; - -export type AttribType = "bool" | "float" | "int" | "vec2" | "vec3" | "vec4"; - -export type AttribBufferData = - | Int8Array - | Uint8Array - | Uint8ClampedArray - | Int16Array - | Uint16Array - | Float32Array; - -export type IndexBufferData = Uint16Array | Uint32Array; - -export type ModelAttributeSpecs = IObjectOf; - -export type UniformValue = number | number[] | TypedArray; - -export type UniformValues = IObjectOf< - UniformValue | Fn2 | IDeref ->; - -export type ShaderType = "vs" | "fs"; - -export type GLSLScalarType = - | "bool" - | "float" - | "int" - | "uint" - | "sampler2D" - | "samplerCube"; - -export type GLSLArrayType = - | "bool[]" - | "int[]" - | "uint[]" - | "float[]" - | "bvec2[]" - | "bvec3[]" - | "bvec4[]" - | "ivec2[]" - | "ivec3[]" - | "ivec4[]" - | "uvec2[]" - | "uvec3[]" - | "uvec4[]" - | "vec2[]" - | "vec3[]" - | "vec4[]" - | "mat2[]" - | "mat3[]" - | "mat4[]" - // | "mat2x3[]" - // | "mat2x4[]" - // | "mat3x2[]" - // | "mat3x4[]" - // | "mat4x2[]" - // | "mat4x3[]" - | "sampler2D[]" - | "sampler3D[]" - | "samplerCube[]"; - -export type UniformDefault = - | T - | Fn2, T>; - -export type UniformDecl = - | GLSL - | [GLSLScalarType, UniformDefault] - | ["bvec2", UniformDefault] - | ["bvec3", UniformDefault] - | ["bvec4", UniformDefault] - | ["ivec2", UniformDefault] - | ["ivec3", UniformDefault] - | ["ivec4", UniformDefault] - | ["vec2", UniformDefault] - | ["vec3", UniformDefault] - | ["vec4", UniformDefault] - | ["mat2", UniformDefault] - | ["mat3", UniformDefault] - | ["mat4", UniformDefault] - // | ["mat2x3", UniformDefault] - // | ["mat2x4", UniformDefault] - // | ["mat3x2", UniformDefault] - // | ["mat3x4", UniformDefault] - // | ["mat4x2", UniformDefault] - // | ["mat4x3", UniformDefault] - | ["bool[]", number, UniformDefault?] - | ["int[]", number, UniformDefault?] - | ["uint[]", number, UniformDefault?] - | ["float[]", number, UniformDefault?] - | ["bvec2[]", number, UniformDefault?] - | ["bvec3[]", number, UniformDefault?] - | ["bvec4[]", number, UniformDefault?] - | ["ivec2[]", number, UniformDefault?] - | ["ivec3[]", number, UniformDefault?] - | ["ivec4[]", number, UniformDefault?] - | ["uvec2[]", number, UniformDefault?] - | ["uvec3[]", number, UniformDefault?] - | ["uvec4[]", number, UniformDefault?] - | ["vec2[]", number, UniformDefault?] - | ["vec3[]", number, UniformDefault?] - | ["vec4[]", number, UniformDefault?] - | ["mat2[]", number, UniformDefault?] - | ["mat3[]", number, UniformDefault?] - | ["mat4[]", number, UniformDefault?] - // | ["mat2x3[]", number, UniformDefault?] - // | ["mat2x4[]", number, UniformDefault?] - // | ["mat3x2[]", number, UniformDefault?] - // | ["mat3x4[]", number, UniformDefault?] - // | ["mat4x2[]", number, UniformDefault?] - // | ["mat4x3[]", number, UniformDefault?] - | ["sampler2D[]", number, UniformDefault?] - | ["sampler3D[]", number, UniformDefault?] - | ["samplerCube[]", number, UniformDefault?]; - -/** - * Object of attribute types w/ optional locations. - */ -export type ShaderAttribSpecs = IObjectOf; - -export type ShaderAttribSpec = AttribType | [AttribType, number]; - -/** - * Object of instantiated shader attributes. - */ -export type ShaderAttribs = IObjectOf; - -export interface ShaderAttrib { - type: AttribType; - loc: number; -} - -export type ShaderVaryingSpecs = IObjectOf; - -export type ShaderVaryingSpec = GLSL | [GLSLArrayType, number]; - -export type ShaderUniformSpecs = IObjectOf; - -export type ShaderUniforms = IObjectOf; - -export type ShaderOutputSpecs = IObjectOf; - -export type ShaderOutputSpec = GLSL | [GLSL, number]; - -export interface ShaderUniform { - type: GLSL; - loc: WebGLUniformLocation; - setter: Fn; - defaultFn?: (shaderUnis: any, specUnis: any) => UniformValue; - defaultVal?: UniformValue; -} - -export interface GLSLSyntax { - number: number; - attrib: Fn3; - uniform: Fn3; - varying: Record< - ShaderType, - Fn3 - >; - output: Fn3; -} - -export interface GLSLDeclPrefixes { - a: string; - v: string; - u: string; - o: string; -} - -export type GLSLExtensionBehavior = "require" | "warn" | boolean; - -export interface ShaderSnippet { - /** - * Array of dependent snippets. - */ - deps?: ShaderSnippet[]; - /** - * Snippet source code. - */ - src: string; -} - -export const DEFAULT_OUTPUT: ShaderOutputSpecs = { fragColor: ["vec4", 0] }; - -export type ShaderFn = Fn4< - GLSLTarget, - IObjectOf>, // uni - IObjectOf>, // attribs - IObjectOf>, // vary - (Sym | Func)[] ->; - -export interface ShaderSpec { - /** - * Vertex shader GLSL source code. - */ - vs: string | ShaderFn; - /** - * Fragment shader GLSL source code. - */ - fs: string | ShaderFn; - /** - * Attribute type declarations. - */ - attribs: ShaderAttribSpecs; - /** - * Varying type declarations. - */ - varying?: ShaderVaryingSpecs; - /** - * Uniform type declarations with optional defaults. - */ - uniforms?: ShaderUniformSpecs; - /** - * WebGL2 only. Fragment shader output variable type declarations. - * Default: `{ fragColor: GLSL.vec4 }` - */ - outputs?: ShaderOutputSpecs; - /** - * Flag to indicate code generation for attribs, varying, uniforms - * and outputs. Default: true. - */ - generateDecls?: boolean; - /** - * Variable naming convention variable prefixes for GLSL code gen. - * - * Defaults: - * - * - Attributes: `a_` - * - Varying: `v_` - * - Uniforms: `u_` - * - Outputs: `o_` - */ - declPrefixes?: Partial; - /** - * Optional prelude source, prepended before main shader code, the - * default prelude (unless disabled) and any other generated code. - */ - pre?: string; - /** - * Optional source code to be appended after main shader code. - */ - post?: string; - /** - * If true, disables default prelude. Default: false - */ - replacePrelude?: boolean; - /** - * Optional shader drawing state flags. Default: none. - */ - state?: Partial; - /** - * WebGL extension config for code generation. Keys in this object - * are extension names and their values specify the desired - * behavior. Boolean values will be translated in "enable" / - * "disable". - */ - ext?: IObjectOf; -} - -export interface ShaderState { - /** - * Enable depth test - */ - depth: boolean; - /** - * Cull faces - */ - cull: boolean; - /** - * Cull mode - */ - cullMode: GLenum; - /** - * Enable blending - */ - blend: boolean; - /** - * 2-element array of glBlendFunction coefficients - * (default: `[gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA]`) - */ - blendFn: Tuple; - /** - * glBlendEquation mode - */ - blendEq: GLenum; - /** - * Enable stencil test - */ - stencil: boolean; - /** - * glStencilFn params - */ - stencilFn: Tuple; - /** - * glStencilOp params - */ - stencilOp: Tuple; - /** - * glStencilMask arg - */ - stencilMask: GLenum; -} - -export interface ShaderOpts { - instancePos: string; - instanceColor: string; - color: string; - uv: string; - material: Partial; - state: Partial; -} - -export interface IShader extends IBind, IRelease { - gl: WebGLRenderingContext; - attribs: IObjectOf; - uniforms: ShaderUniforms; - - bindAttribs(specAttribs: ModelAttributeSpecs): void; - bindUniforms(specUnis: UniformValues): void; - prepareState(state?: Partial): void; -} - -export interface IWebGLBuffer extends IBind, IRelease { - set(data: T, mode?: GLenum): void; - setChunk(data: T, offset: number): void; -} - -export interface IConfigure { - configure(opts: T): boolean; -} - -export interface ITexture - extends IBind, - IConfigure>, - IRelease { - tex: WebGLTexture; -} - -export interface IFbo - extends IBind, - IConfigure>, - IRelease {} - -export interface IRenderBuffer extends IBind, IRelease { - buffer: WebGLRenderbuffer; - format: GLenum; - width: number; - height: number; -} - -export interface ModelSpec { - /** - * Initialized `IShader` instance - */ - shader: IShader; - /** - * GLSL attribute declarations - */ - attribs: ModelAttributeSpecs; - /** - * Geometry attributes given as `AttribPool` instance. - */ - attribPool?: AttribPool; - /** - * GLSL uniform value overrides - */ - uniforms?: UniformValues; - /** - * Buffer spec for indexed geometry - */ - indices?: IndexBufferSpec; - /** - * Array of initialized `ITexture` instances. - * Each non-null item will be auto-bound to its respective texture unit, - * each time the model is drawn via `draw()` - */ - textures?: ITexture[]; - /** - * Extra configuration for instanced geometry - */ - instances?: InstancingSpec; - /** - * WebGL draw mode. Defaults to `TRIANGLES` - */ - mode?: GLenum; - /** - * Number of vertices/indices to draw - */ - num: number; -} - -/** - * Data specification of a single WebGL attribute - */ -export interface ModelAttributeSpec { - /** - * Backing `WebGLArrayBuffer` instance. Usually this will be - * auto-initialized by `compileBuffers()` - */ - buffer?: IWebGLBuffer; - /** - * Raw attribute data from which `buffer` will be initialized - */ - data?: AttribBufferData; - /** - * Attribute element size (in component values, not bytes). - * Default: 3 - */ - size?: number; - /** - * Auto-normalization flag when writing buffer data. - * Default: false - */ - normalized?: boolean; - /** - * Byte offset of 1st attrib component. - * Default: 0 - */ - offset?: number; - /** - * Attribute stride in bytes. - * Default: 0 = densely packed - */ - stride?: number; - /** - * Attribute's WebGL data type. - * Default: gl.FLOAT - */ - type?: GLenum; - /** - * Only used for instanced attributes. - * See: https://www.khronos.org/registry/OpenGL/extensions/ANGLE/ANGLE_instanced_arrays.txt - */ - divisor?: number; -} - -export interface IndexBufferSpec { - /** - * Backing `WebGLBuffer` instance. Usually this will be - * auto-initialized by `makeBuffersInSpec()` - */ - buffer?: IWebGLBuffer; - /** - * Raw attribute data from which `buffer` will be initialized - */ - data: IndexBufferData; -} - -export interface InstancingSpec { - attribs: IObjectOf; - num: number; -} - -export interface TextureOpts { - image: ArrayBufferView | TexImageSource | null; - target: GLenum; - type: GLenum; - filter: GLenum | [GLenum, GLenum?]; - wrap: GLenum | [GLenum, GLenum?, GLenum?]; - lod: [GLenum, GLenum?]; - minMaxLevel: [GLenum, GLenum]; - level: GLenum; - format: GLenum; - internalFormat: GLenum; - width: number; - height: number; - mipmap: boolean; - flip: boolean; - premultiply: boolean; - sub: boolean; - pos: number[]; -} - -export interface FboOpts { - /** - * Array of Texture instances to be used as color attachments. - * Multiple attachments are only allowed if the `webgl_draw_buffers` - * extension is available. The texture at `[0]` will be mapped to - * `COLOR_ATTACHMENT0` (or `COLOR_ATTACHMENT0_WEBGL`), other indices - * are mapped to their respective attachment IDs. - */ - tex: ITexture[]; - /** - * Optional pre-instantiated `RenderBuffer` to be used as depth - * buffer for this FBO. - */ - depth?: ITexture | IRenderBuffer; -} - -export interface RboOpts { - format?: number; - width: number; - height: number; -} - -export interface Material { - ambientCol: GLVec3; - diffuseCol: GLVec3; - specularCol: GLVec3; -} - -export interface WeblGLCanvasOpts { - canvas: string | HTMLCanvasElement; - parent: HTMLElement; - opts: Partial; - version: 1 | 2; - width: number; - height: number; - autoScale: boolean; - onContextLost: EventListener; - ext: (keyof WebGLExtensionMap)[]; -} - -export interface GPGPUOpts { - size: number; - inputs?: number | GPGPUTextureConfig[]; - outputs?: number | GPGPUTextureConfig[]; - gl?: WebGLRenderingContext; - version?: 1 | 2; -} - -export interface GPGPUTextureConfig - extends Partial< - Pick - > { - stride: number; -} - -export interface GPGPUJobConfig { - shader: ShaderSpec; - src: string | ShaderFn; - uniforms: ShaderUniformSpecs; - inputs: number; - outputs?: number; -} - -export interface GPGPUJobExecOpts { - inputs: (ITexture | TypedArray)[]; - outputs?: number[]; - uniforms?: UniformValues; -} - -export const GL_COLOR_ATTACHMENT0_WEBGL = 0x8ce0; -export const GL_MAX_COLOR_ATTACHMENTS_WEBGL = 0x8cdf; -export const GL_RGBA = 0x1908; -export const GL_RGBA32F = 0x8814; - -// [SRC_ALPHA, ONE_MINUS_SRC_ALPHA] -export const DEFAULT_BLEND: Tuple = [0x302, 0x303]; - -export const GL_EXT_INFO = { - WEBGL_draw_buffers: { - gl: true, - alias: "GL_EXT_draw_buffers" - }, - OES_standard_derivatives: { - gl: true, - alias: "GL_OES_standard_derivatives" - } -}; - -export interface WebGLExtensionMap { - EXT_blend_minmax: EXT_blend_minmax; - EXT_color_buffer_float: WEBGL_color_buffer_float; - EXT_texture_filter_anisotropic: EXT_texture_filter_anisotropic; - EXT_frag_depth: EXT_frag_depth; - EXT_shader_texture_lod: EXT_shader_texture_lod; - EXT_sRGB: EXT_sRGB; - OES_vertex_array_object: OES_vertex_array_object; - WEBGL_color_buffer_float: WEBGL_color_buffer_float; - WEBGL_compressed_texture_astc: WEBGL_compressed_texture_astc; - WEBGL_compressed_texture_s3tc_srgb: WEBGL_compressed_texture_s3tc_srgb; - WEBGL_debug_shaders: WEBGL_debug_shaders; - WEBGL_draw_buffers: WEBGL_draw_buffers; - WEBGL_lose_context: WEBGL_lose_context; - WEBGL_depth_texture: WEBGL_depth_texture; - WEBGL_debug_renderer_info: WEBGL_debug_renderer_info; - WEBGL_compressed_texture_s3tc: WEBGL_compressed_texture_s3tc; - OES_texture_half_float_linear: OES_texture_half_float_linear; - OES_texture_half_float: OES_texture_half_float; - OES_texture_float_linear: OES_texture_float_linear; - OES_texture_float: OES_texture_float; - OES_standard_derivatives: OES_standard_derivatives; - OES_element_index_uint: OES_element_index_uint; - ANGLE_instanced_arrays: ANGLE_instanced_arrays; -} diff --git a/packages/webgl/src/api/blend.ts b/packages/webgl/src/api/blend.ts new file mode 100644 index 0000000000..63fb59d669 --- /dev/null +++ b/packages/webgl/src/api/blend.ts @@ -0,0 +1,39 @@ +import { Tuple } from "@thi.ng/api"; + +export const enum Blend { + ZERO = 0, + ONE = 1, + SRC_COLOR = 768, + ONE_MINUS_SRC_COLOR = 769, + DST_COLOR = 774, + ONE_MINUS_DST_COLOR = 775, + SRC_ALPHA = 770, + ONE_MINUS_SRC_ALPHA = 771, + DST_ALPHA = 772, + ONE_MINUS_DST_ALPHA = 773, + CONSTANT_COLOR = 32769, + ONE_MINUS_CONSTANT_COLOR = 32770, + CONSTANT_ALPHA = 32771, + ONE_MINUS_CONSTANT_ALPHA = 32772, + SRC_ALPHA_SATURATE = 776 +} + +export const enum BlendEquation { + FUNC_ADD = 32774, + FUNC_REVERSE_SUBTRACT = 32779, + FUNC_SUBTRACT = 32778, + MAX = 32776, + MIN = 32775 +} + +export type BlendFunc = Tuple; + +// TODO blend func presets +// https://www.andersriggelsen.dk/glblendfunc.php + +export const BLEND_NORMAL: BlendFunc = [ + Blend.SRC_ALPHA, + Blend.ONE_MINUS_SRC_ALPHA +]; + +export const BLEND_ADD: BlendFunc = [Blend.SRC_ALPHA, Blend.DST_ALPHA]; diff --git a/packages/webgl/src/api/buffers.ts b/packages/webgl/src/api/buffers.ts new file mode 100644 index 0000000000..e0b1734314 --- /dev/null +++ b/packages/webgl/src/api/buffers.ts @@ -0,0 +1,59 @@ +import { IBind, IRelease } from "@thi.ng/api"; +import { ITexture } from "./texture"; + +export type IndexBufferData = Uint16Array | Uint32Array; + +export interface IWebGLBuffer extends IBind, IRelease { + set(data: T, mode?: GLenum): void; + setChunk(data: T, offset: number): void; +} + +export interface IConfigure { + configure(opts: T): boolean; +} + +export interface IFbo + extends IBind, + IConfigure>, + IRelease {} + +export interface IRenderBuffer extends IBind, IRelease { + buffer: WebGLRenderbuffer; + format: GLenum; + width: number; + height: number; +} + +export interface IndexBufferSpec { + /** + * Backing `WebGLBuffer` instance. Usually this will be + * auto-initialized by `makeBuffersInSpec()` + */ + buffer?: IWebGLBuffer; + /** + * Raw attribute data from which `buffer` will be initialized + */ + data: IndexBufferData; +} + +export interface FboOpts { + /** + * Array of Texture instances to be used as color attachments. + * Multiple attachments are only allowed if the `webgl_draw_buffers` + * extension is available. The texture at `[0]` will be mapped to + * `COLOR_ATTACHMENT0` (or `COLOR_ATTACHMENT0_WEBGL`), other indices + * are mapped to their respective attachment IDs. + */ + tex: ITexture[]; + /** + * Optional pre-instantiated `RenderBuffer` to be used as depth + * buffer for this FBO. + */ + depth?: ITexture | IRenderBuffer; +} + +export interface RboOpts { + format?: number; + width: number; + height: number; +} diff --git a/packages/webgl/src/api/canvas.ts b/packages/webgl/src/api/canvas.ts new file mode 100644 index 0000000000..5fbd3752d1 --- /dev/null +++ b/packages/webgl/src/api/canvas.ts @@ -0,0 +1,13 @@ +import { WebGLExtensionMap } from "./ext"; + +export interface WeblGLCanvasOpts { + canvas: string | HTMLCanvasElement; + parent: HTMLElement; + opts: Partial; + version: 1 | 2; + width: number; + height: number; + autoScale: boolean; + onContextLost: EventListener; + ext: (keyof WebGLExtensionMap)[]; +} diff --git a/packages/webgl/src/api/ext.ts b/packages/webgl/src/api/ext.ts new file mode 100644 index 0000000000..b363e76822 --- /dev/null +++ b/packages/webgl/src/api/ext.ts @@ -0,0 +1,52 @@ +import { IObjectOf } from "@thi.ng/api"; + +export const GL_EXT_INFO: IObjectOf = { + WEBGL_draw_buffers: { + gl: true, + alias: "GL_EXT_draw_buffers" + }, + OES_standard_derivatives: { + gl: true, + alias: "GL_OES_standard_derivatives" + } +}; + +export interface ExtensionInfo { + gl?: boolean; + gl2?: boolean; + alias: string; +} + +export interface WebGLExtensionMap { + ANGLE_instanced_arrays: ANGLE_instanced_arrays; + EXT_blend_minmax: EXT_blend_minmax; + EXT_color_buffer_float: WEBGL_color_buffer_float; + EXT_frag_depth: EXT_frag_depth; + EXT_shader_texture_lod: EXT_shader_texture_lod; + EXT_sRGB: EXT_sRGB; + EXT_texture_filter_anisotropic: EXT_texture_filter_anisotropic; + OES_element_index_uint: OES_element_index_uint; + OES_standard_derivatives: OES_standard_derivatives; + OES_texture_float_linear: OES_texture_float_linear; + OES_texture_float: OES_texture_float; + OES_texture_half_float_linear: OES_texture_half_float_linear; + OES_texture_half_float: OES_texture_half_float; + OES_vertex_array_object: OES_vertex_array_object; + WEBGL_color_buffer_float: WEBGL_color_buffer_float; + WEBGL_compressed_texture_astc: WEBGL_compressed_texture_astc; + WEBGL_compressed_texture_s3tc_srgb: WEBGL_compressed_texture_s3tc_srgb; + WEBGL_compressed_texture_s3tc: WEBGL_compressed_texture_s3tc; + WEBGL_debug_renderer_info: WEBGL_debug_renderer_info; + WEBGL_debug_shaders: WEBGL_debug_shaders; + WEBGL_depth_texture: WEBGL_depth_texture; + WEBGL_draw_buffers: WEBGL_draw_buffers; + WEBGL_lose_context: WEBGL_lose_context; +} + +export type ExtensionName = keyof WebGLExtensionMap; + +export type ExtensionBehavior = "require" | "warn" | boolean; + +export type ExtensionBehaviors = Partial< + Record +>; diff --git a/packages/webgl/src/api/glsl.ts b/packages/webgl/src/api/glsl.ts new file mode 100644 index 0000000000..d37442956c --- /dev/null +++ b/packages/webgl/src/api/glsl.ts @@ -0,0 +1,60 @@ +import { Tuple } from "@thi.ng/api"; +import { Type } from "@thi.ng/shader-ast"; + +export type GLSL = Type; + +export type GLVec = number[] | Float32Array; +export type GLVec2 = Tuple | Float32Array; +export type GLVec3 = Tuple | Float32Array; +export type GLVec4 = Tuple | Float32Array; + +export type GLIntVec = number[] | Int32Array; +export type GLUintVec = number[] | Uint32Array; +export type GLIntVec2 = Tuple | Int32Array; +export type GLIntVec3 = Tuple | Int32Array; +export type GLIntVec4 = Tuple | Int32Array; + +export type GLMat2 = Tuple | Float32Array; +export type GLMat3 = Tuple | Float32Array; +export type GLMat4 = Tuple | Float32Array; +export type GLMat23 = Tuple | Float32Array; +export type GLMat24 = Tuple | Float32Array; +export type GLMat34 = Tuple | Float32Array; + +export type GLSLScalarType = + | "bool" + | "float" + | "int" + | "uint" + | "sampler2D" + | "samplerCube"; + +export type GLSLArrayType = + | "bool[]" + | "int[]" + | "uint[]" + | "float[]" + | "bvec2[]" + | "bvec3[]" + | "bvec4[]" + | "ivec2[]" + | "ivec3[]" + | "ivec4[]" + | "uvec2[]" + | "uvec3[]" + | "uvec4[]" + | "vec2[]" + | "vec3[]" + | "vec4[]" + | "mat2[]" + | "mat3[]" + | "mat4[]" + // | "mat2x3[]" + // | "mat2x4[]" + // | "mat3x2[]" + // | "mat3x4[]" + // | "mat4x2[]" + // | "mat4x3[]" + | "sampler2D[]" + | "sampler3D[]" + | "samplerCube[]"; diff --git a/packages/webgl/src/api/logger.ts b/packages/webgl/src/api/logger.ts new file mode 100644 index 0000000000..76c7927ec3 --- /dev/null +++ b/packages/webgl/src/api/logger.ts @@ -0,0 +1,5 @@ +import { ILogger, NULL_LOGGER } from "@thi.ng/api"; + +export let LOGGER: ILogger = NULL_LOGGER; + +export const setLogger = (logger: ILogger) => (LOGGER = logger); diff --git a/packages/webgl/src/api/material.ts b/packages/webgl/src/api/material.ts new file mode 100644 index 0000000000..f9459f20a6 --- /dev/null +++ b/packages/webgl/src/api/material.ts @@ -0,0 +1,7 @@ +import { GLVec3 } from "./glsl"; + +export interface Material { + ambientCol: GLVec3; + diffuseCol: GLVec3; + specularCol: GLVec3; +} diff --git a/packages/webgl/src/api/model.ts b/packages/webgl/src/api/model.ts new file mode 100644 index 0000000000..f03f7dba27 --- /dev/null +++ b/packages/webgl/src/api/model.ts @@ -0,0 +1,98 @@ +import { IObjectOf } from "@thi.ng/api"; +import { AttribPool } from "@thi.ng/vector-pools"; +import { IndexBufferSpec, IWebGLBuffer } from "./buffers"; +import { AttribBufferData, IShader, UniformValues } from "./shader"; +import { ITexture } from "./texture"; + +export type ModelAttributeSpecs = IObjectOf; + +export interface ModelSpec { + /** + * Initialized `IShader` instance + */ + shader: IShader; + /** + * GLSL attribute declarations + */ + attribs: ModelAttributeSpecs; + /** + * Geometry attributes given as `AttribPool` instance. + */ + attribPool?: AttribPool; + /** + * GLSL uniform value overrides + */ + uniforms?: UniformValues; + /** + * Buffer spec for indexed geometry + */ + indices?: IndexBufferSpec; + /** + * Array of initialized `ITexture` instances. + * Each non-null item will be auto-bound to its respective texture unit, + * each time the model is drawn via `draw()` + */ + textures?: ITexture[]; + /** + * Extra configuration for instanced geometry + */ + instances?: InstancingSpec; + /** + * WebGL draw mode. Defaults to `TRIANGLES` + */ + mode?: GLenum; + /** + * Number of vertices/indices to draw + */ + num: number; +} + +/** + * Data specification of a single WebGL attribute + */ +export interface ModelAttributeSpec { + /** + * Backing `WebGLArrayBuffer` instance. Usually this will be + * auto-initialized by `compileBuffers()` + */ + buffer?: IWebGLBuffer; + /** + * Raw attribute data from which `buffer` will be initialized + */ + data?: AttribBufferData; + /** + * Attribute element size (in component values, not bytes). + * Default: 3 + */ + size?: number; + /** + * Auto-normalization flag when writing buffer data. + * Default: false + */ + normalized?: boolean; + /** + * Byte offset of 1st attrib component. + * Default: 0 + */ + offset?: number; + /** + * Attribute stride in bytes. + * Default: 0 = densely packed + */ + stride?: number; + /** + * Attribute's WebGL data type. + * Default: gl.FLOAT + */ + type?: GLenum; + /** + * Only used for instanced attributes. + * See: https://www.khronos.org/registry/OpenGL/extensions/ANGLE/ANGLE_instanced_arrays.txt + */ + divisor?: number; +} + +export interface InstancingSpec { + attribs: IObjectOf; + num: number; +} diff --git a/packages/webgl/src/api/multipass.ts b/packages/webgl/src/api/multipass.ts new file mode 100644 index 0000000000..8de762f002 --- /dev/null +++ b/packages/webgl/src/api/multipass.ts @@ -0,0 +1,68 @@ +import { IObjectOf } from "@thi.ng/api"; +import { AttribPool } from "@thi.ng/vector-pools"; +import { IFbo, IndexBufferSpec } from "./buffers"; +import { InstancingSpec, ModelAttributeSpecs, ModelSpec } from "./model"; +import { + ShaderAttribSpecs, + ShaderFn, + ShaderState, + ShaderVaryingSpecs, + UniformDecl, + UniformValues +} from "./shader"; +import { ITexture, TextureOpts } from "./texture"; + +export interface Multipass { + start(): void; + stop(): void; + update(time?: number): void; + + fbos: IFbo[]; + models: ModelSpec[]; + passes: PassOpts[]; + textures: IObjectOf; +} + +export interface MultipassOpts { + gl: WebGLRenderingContext; + textures: IObjectOf>; + passes: PassOpts[]; + width: number; + height: number; + uniforms?: Partial; + uniformVals?: UniformValues; +} + +export interface PassOpts { + vs?: string | ShaderFn; + fs: string | ShaderFn; + model?: PassModelSpec; + inputs: string[]; + outputs: string[]; + attribs?: ShaderAttribSpecs; + varying?: ShaderVaryingSpecs; + uniforms?: Partial; + uniformVals?: UniformValues; + pre?: string; + post?: string; + replacePrelude?: boolean; + generateDecls?: boolean; + state?: Partial; +} + +export interface PassUniforms { + inputs: never; + outputs: never; + resolution: "vec2"; + time: "float"; + [id: string]: UniformDecl; +} + +export interface PassModelSpec { + attribs: ModelAttributeSpecs; + attribPool?: AttribPool; + indices?: IndexBufferSpec; + instances?: InstancingSpec; + mode?: GLenum; + num: number; +} diff --git a/packages/webgl/src/api/shader.ts b/packages/webgl/src/api/shader.ts new file mode 100644 index 0000000000..c0f97bfd5b --- /dev/null +++ b/packages/webgl/src/api/shader.ts @@ -0,0 +1,303 @@ +import { + Fn, + Fn2, + Fn3, + Fn4, + IBind, + IDeref, + IObjectOf, + IRelease, + NumericArray +} from "@thi.ng/api"; +import { Func, Sym } from "@thi.ng/shader-ast"; +import { GLSLTarget } from "@thi.ng/shader-ast-glsl"; +import { ReadonlyVec } from "@thi.ng/vectors"; +import { BlendEquation, BlendFunc } from "./blend"; +import { ExtensionBehaviors } from "./ext"; +import { + GLIntVec, + GLIntVec2, + GLIntVec3, + GLIntVec4, + GLMat2, + GLMat3, + GLMat4, + GLSL, + GLSLArrayType, + GLSLScalarType, + GLUintVec, + GLVec, + GLVec2, + GLVec3, + GLVec4 +} from "./glsl"; +import { ModelAttributeSpecs, ModelSpec } from "./model"; +import { StencilFnParams, StencilOpParams } from "./stencil"; + +export interface GLSLSyntax { + number: number; + attrib: Fn3; + uniform: Fn3; + varying: Record< + ShaderType, + Fn3 + >; + output: Fn3; +} + +export interface GLSLDeclPrefixes { + a: string; + v: string; + u: string; + o: string; +} + +export type ShaderType = "vs" | "fs"; + +export type AttribType = "bool" | "float" | "int" | "vec2" | "vec3" | "vec4"; + +export type AttribBufferData = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Float32Array; + +export type UniformValue = number | NumericArray; + +export type UniformValues = IObjectOf< + UniformValue | Fn2 | IDeref +>; + +export type UniformDefault = + | T + | Fn2, T>; + +export type UniformDecl = + | GLSL + | [GLSLScalarType, UniformDefault] + | ["bvec2", UniformDefault] + | ["bvec3", UniformDefault] + | ["bvec4", UniformDefault] + | ["ivec2", UniformDefault] + | ["ivec3", UniformDefault] + | ["ivec4", UniformDefault] + | ["vec2", UniformDefault] + | ["vec3", UniformDefault] + | ["vec4", UniformDefault] + | ["mat2", UniformDefault] + | ["mat3", UniformDefault] + | ["mat4", UniformDefault] + // | ["mat2x3", UniformDefault] + // | ["mat2x4", UniformDefault] + // | ["mat3x2", UniformDefault] + // | ["mat3x4", UniformDefault] + // | ["mat4x2", UniformDefault] + // | ["mat4x3", UniformDefault] + | ["bool[]", number, UniformDefault?] + | ["int[]", number, UniformDefault?] + | ["uint[]", number, UniformDefault?] + | ["float[]", number, UniformDefault?] + | ["bvec2[]", number, UniformDefault?] + | ["bvec3[]", number, UniformDefault?] + | ["bvec4[]", number, UniformDefault?] + | ["ivec2[]", number, UniformDefault?] + | ["ivec3[]", number, UniformDefault?] + | ["ivec4[]", number, UniformDefault?] + | ["uvec2[]", number, UniformDefault?] + | ["uvec3[]", number, UniformDefault?] + | ["uvec4[]", number, UniformDefault?] + | ["vec2[]", number, UniformDefault?] + | ["vec3[]", number, UniformDefault?] + | ["vec4[]", number, UniformDefault?] + | ["mat2[]", number, UniformDefault?] + | ["mat3[]", number, UniformDefault?] + | ["mat4[]", number, UniformDefault?] + // | ["mat2x3[]", number, UniformDefault?] + // | ["mat2x4[]", number, UniformDefault?] + // | ["mat3x2[]", number, UniformDefault?] + // | ["mat3x4[]", number, UniformDefault?] + // | ["mat4x2[]", number, UniformDefault?] + // | ["mat4x3[]", number, UniformDefault?] + | ["sampler2D[]", number, UniformDefault?] + | ["sampler3D[]", number, UniformDefault?] + | ["samplerCube[]", number, UniformDefault?]; + +/** + * Object of attribute types w/ optional locations. + */ +export type ShaderAttribSpecs = IObjectOf; + +export type ShaderAttribSpec = AttribType | [AttribType, number]; + +/** + * Object of instantiated shader attributes. + */ +export type ShaderAttribs = IObjectOf; + +export interface ShaderAttrib { + type: AttribType; + loc: number; +} + +export type ShaderVaryingSpecs = IObjectOf; + +export type ShaderVaryingSpec = GLSL | [GLSLArrayType, number]; + +export type ShaderUniformSpecs = IObjectOf; + +export type ShaderUniforms = IObjectOf; + +export type ShaderOutputSpecs = IObjectOf; + +export type ShaderOutputSpec = GLSL | [GLSL, number]; + +export interface ShaderUniform { + type: GLSL; + loc: WebGLUniformLocation; + setter: Fn; + defaultFn?: (shaderUnis: any, specUnis: any) => UniformValue; + defaultVal?: UniformValue; +} + +export const DEFAULT_OUTPUT: ShaderOutputSpecs = { fragColor: ["vec4", 0] }; + +export type ShaderFn = Fn4< + GLSLTarget, + IObjectOf>, // uni + IObjectOf>, // attribs + IObjectOf>, // vary + (Sym | Func)[] +>; + +export interface ShaderSpec { + /** + * Vertex shader GLSL source code. + */ + vs: string | ShaderFn; + /** + * Fragment shader GLSL source code. + */ + fs: string | ShaderFn; + /** + * Attribute type declarations. + */ + attribs: ShaderAttribSpecs; + /** + * Varying type declarations. + */ + varying?: ShaderVaryingSpecs; + /** + * Uniform type declarations with optional defaults. + */ + uniforms?: ShaderUniformSpecs; + /** + * WebGL2 only. Fragment shader output variable type declarations. + * Default: `{ fragColor: GLSL.vec4 }` + */ + outputs?: ShaderOutputSpecs; + /** + * Flag to indicate code generation for attribs, varying, uniforms + * and outputs. Default: true. + */ + generateDecls?: boolean; + /** + * Variable naming convention variable prefixes for GLSL code gen. + * + * Defaults: + * + * - Attributes: `a_` + * - Varying: `v_` + * - Uniforms: `u_` + * - Outputs: `o_` + */ + declPrefixes?: Partial; + /** + * Optional prelude source, prepended before main shader code, the + * default prelude (unless disabled) and any other generated code. + */ + pre?: string; + /** + * Optional source code to be appended after main shader code. + */ + post?: string; + /** + * If true, disables default prelude. Default: false + */ + replacePrelude?: boolean; + /** + * Optional shader drawing state flags. Default: none. + */ + state?: Partial; + /** + * WebGL extension config for code generation. Keys in this object + * are extension names and their values specify the desired + * behavior. Boolean values will be translated in "enable" / + * "disable". + */ + ext?: ExtensionBehaviors; +} + +export interface ShaderState { + /** + * Enable depth test + */ + depth: boolean; + /** + * Cull faces + */ + cull: boolean; + /** + * Cull mode + */ + cullMode: GLenum; + /** + * Enable blending + */ + blend: boolean; + /** + * 2-element array of glBlendFunction coefficients + * (default: `[gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA]`) + */ + blendFn: BlendFunc; + /** + * glBlendEquation mode + */ + blendEq: BlendEquation; + /** + * Enable stencil test + */ + stencil: boolean; + /** + * glStencilFn params + */ + stencilFn: StencilFnParams; + /** + * glStencilOp params + */ + stencilOp: StencilOpParams; + /** + * glStencilMask arg + */ + stencilMask: number; +} + +export interface ShaderOpts { + instancePos: string; + instanceColor: string; + color: string; + uv: string; + material: Partial; + state: Partial; +} + +export interface IShader extends IBind, IRelease { + gl: WebGLRenderingContext; + attribs: IObjectOf; + uniforms: ShaderUniforms; + + bindAttribs(specAttribs: ModelAttributeSpecs): void; + bindUniforms(specUnis: UniformValues): void; + prepareState(state?: Partial): void; +} diff --git a/packages/webgl/src/api/stencil.ts b/packages/webgl/src/api/stencil.ts new file mode 100644 index 0000000000..5e8aa76cf2 --- /dev/null +++ b/packages/webgl/src/api/stencil.ts @@ -0,0 +1,27 @@ +import { Tuple } from "@thi.ng/api"; + +export const enum StencilOp { + KEEP = 7680, + ZERO = 0, + REPLACE = 7681, + INCR = 7682, + INCR_WRAP = 34055, + DECR = 7683, + DECR_WRAP = 34056, + INVERT = 5386 +} + +export const enum StencilFn { + NEVER = 512, + LESS = 513, + EQUAL = 514, + LEQUAL = 515, + GREATER = 516, + NOTEQUAL = 517, + GEQUAL = 518, + ALWAYS = 519 +} + +export type StencilOpParams = Tuple; + +export type StencilFnParams = [StencilFn, number, number]; diff --git a/packages/webgl/src/api/texture.ts b/packages/webgl/src/api/texture.ts new file mode 100644 index 0000000000..8b8c69942f --- /dev/null +++ b/packages/webgl/src/api/texture.ts @@ -0,0 +1,665 @@ +import { IBind, IObjectOf, IRelease } from "@thi.ng/api"; +import { IConfigure } from "./buffers"; + +export const enum TextureFormat { + ALPHA = 0x1906, + DEPTH_COMPONENT = 0x1902, + DEPTH_COMPONENT16 = 0x81a5, + DEPTH_COMPONENT24 = 0x81a6, + DEPTH_COMPONENT32F = 0x8cac, + DEPTH_STENCIL = 0x84f9, + DEPTH24_STENCIL8 = 0x88f0, + DEPTH32F_STENCIL8 = 0x8cad, + LUMINANCE = 0x1909, + LUMINANCE_ALPHA = 0x190a, + R11F_G11F_B10F = 0x8c3a, + R16F = 0x822d, + R16I = 0x8233, + R16UI = 0x8234, + R32F = 0x822e, + R32I = 0x8235, + R32UI = 0x8236, + R8 = 0x8229, + R8_SNORM = 0x8f94, + R8I = 0x8231, + R8UI = 0x8232, + RED = 0x1903, + RED_INTEGER = 0x8d94, + RG = 0x8227, + RG_INTEGER = 0x8228, + RG16F = 0x822f, + RG16I = 0x8239, + RG16UI = 0x823a, + RG32F = 0x8230, + RG32I = 0x823b, + RG32UI = 0x823c, + RG8 = 0x822b, + RG8_SNORM = 0x8f95, + RG8I = 0x8237, + RG8UI = 0x8238, + RGB = 0x1907, + RGB_INTEGER = 0x8d98, + RGB10_A2 = 0x8059, + RGB10_A2UI = 0x906f, + RGB16F = 0x881b, + RGB16I = 0x8d89, + RGB16UI = 0x8d77, + RGB32F = 0x8815, + RGB32I = 0x8d83, + RGB32UI = 0x8d71, + RGB5_A1 = 0x8057, + RGB565 = 0x8d62, + RGB8 = 0x8051, + RGB8_SNORM = 0x8f96, + RGB8I = 0x8d8f, + RGB8UI = 0x8d7d, + RGB9_E5 = 0x8c3d, + RGBA = 0x1908, + RGBA_INTEGER = 0x8d99, + RGBA16F = 0x881a, + RGBA16I = 0x8d88, + RGBA16UI = 0x8d76, + RGBA32F = 0x8814, + RGBA32I = 0x8d82, + RGBA32UI = 0x8d70, + RGBA4 = 0x8056, + RGBA8 = 0x8058, + RGBA8_SNORM = 0x8f97, + RGBA8I = 0x8d8e, + RGBA8UI = 0x8d7c, + SRGB8 = 0x8c41, + SRGB8_ALPHA8 = 0x8c43 +} + +export const enum TextureType { + BYTE = 0x1400, + UNSIGNED_BYTE, + SHORT, + UNSIGNED_SHORT, + INT, + UNSIGNED_INT, + FLOAT, + HALF_FLOAT = 0x140b, + UNSIGNED_SHORT_4_4_4_4 = 0x8033, + UNSIGNED_SHORT_5_5_5_1 = 0x8034, + UNSIGNED_SHORT_5_6_5 = 0x8363, + UNSIGNED_INT_2_10_10_10_REV = 0x8368, + UNSIGNED_INT_24_8 = 0x84fa, + UNSIGNED_INT_10F_11F_11F_REV = 0x8c3b, + UNSIGNED_INT_5_9_9_9_REV = 0x8c3e, + HALF_FLOAT_OES = 0x8d61, + FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8dad +} + +export const enum TextureTarget { + TEXTURE_2D = 3553, + TEXTURE_3D = 32879, + TEXTURE_CUBE_MAP = 34067, + TEXTURE_2D_ARRAY = 35866 +} + +export const enum TextureFilter { + LINEAR = 9729, + NEAREST = 9728, + NEAREST_MIPMAP_NEAREST = 9984, + LINEAR_MIPMAP_NEAREST = 9985, + NEAREST_MIPMAP_LINEAR = 9986, + LINEAR_MIPMAP_LINEAR = 9987 +} + +export const enum TextureRepeat { + REPEAT = 10497, + CLAMP = 33071, + REPEAT_MIRROR = 33648 +} + +export interface TextureFormatDecl { + /** + * Base format + */ + format: TextureFormat; + /** + * Acceptable types and their resulting byte size per pixel. + * Interleaved layout `[format, size, format, size...]` + */ + types: (TextureType | number)[]; + /** + * Number of color components + */ + num: number; + /** + * Format is renderable + */ + render?: boolean; + /** + * Format is renderable via extension + */ + renderExt?: boolean; + /** + * Format is filterable (other than GL_NEAREST) + */ + filter?: boolean; + /** + * WebGL 2 only + */ + gl2?: boolean; +} + +const $ = ( + format: TextureFormat, + types: (TextureType | number)[], + num: number, + render = false, + filter = false, + renderExt = render +) => ({ + format, + types, + render, + renderExt, + filter, + num +}); + +export const TEX_FORMATS: IObjectOf = { + [TextureFormat.ALPHA]: $( + TextureFormat.ALPHA, + [ + TextureType.UNSIGNED_BYTE, + 1, + TextureType.HALF_FLOAT, + 2, + TextureType.HALF_FLOAT_OES, + 2, + TextureType.FLOAT, + 4 + ], + 1, + true, + true + ), + [TextureFormat.DEPTH_COMPONENT16]: $( + TextureFormat.DEPTH_COMPONENT, + [TextureType.UNSIGNED_SHORT, 2, TextureType.UNSIGNED_INT, 4], + 1, + true + ), + [TextureFormat.DEPTH_COMPONENT24]: $( + TextureFormat.DEPTH_COMPONENT, + [TextureType.UNSIGNED_INT, 4], + 1, + true + ), + [TextureFormat.DEPTH_COMPONENT32F]: $( + TextureFormat.DEPTH_COMPONENT, + [TextureType.FLOAT, 4], + 1, + true + ), + [TextureFormat.DEPTH24_STENCIL8]: $( + TextureFormat.DEPTH_STENCIL, + [TextureType.UNSIGNED_INT_24_8, 4], + 1, + true + ), + [TextureFormat.DEPTH32F_STENCIL8]: $( + TextureFormat.DEPTH_STENCIL, + [TextureType.FLOAT_32_UNSIGNED_INT_24_8_REV, 4], + 1, + true + ), + [TextureFormat.LUMINANCE_ALPHA]: $( + TextureFormat.LUMINANCE_ALPHA, + [ + TextureType.UNSIGNED_BYTE, + 2, + TextureType.HALF_FLOAT, + 4, + TextureType.HALF_FLOAT_OES, + 4, + TextureType.FLOAT, + 8 + ], + 2, + true, + true + ), + [TextureFormat.LUMINANCE]: $( + TextureFormat.LUMINANCE, + [ + TextureType.UNSIGNED_BYTE, + 1, + TextureType.HALF_FLOAT, + 2, + TextureType.HALF_FLOAT_OES, + 2, + TextureType.FLOAT, + 4 + ], + 1, + true, + true + ), + [TextureFormat.R11F_G11F_B10F]: $( + TextureFormat.RGB, + [ + TextureType.FLOAT, + 12, + TextureType.HALF_FLOAT, + 6, + TextureType.UNSIGNED_INT_10F_11F_11F_REV, + 4 + ], + 3, + false, + true, + true + ), + [TextureFormat.R16F]: $( + TextureFormat.RED, + [TextureType.FLOAT, 4, TextureType.HALF_FLOAT, 2], + 1, + false, + true, + true + ), + [TextureFormat.R16I]: $( + TextureFormat.RED_INTEGER, + [TextureType.SHORT, 2], + 1, + true + ), + [TextureFormat.R16UI]: $( + TextureFormat.RED_INTEGER, + [TextureType.UNSIGNED_SHORT, 2], + 1, + true + ), + [TextureFormat.R32F]: $( + TextureFormat.RED, + [TextureType.FLOAT, 4], + 1, + false, + false, + true + ), + [TextureFormat.R32I]: $( + TextureFormat.RED_INTEGER, + [TextureType.INT, 4], + 1, + true + ), + [TextureFormat.R32UI]: $( + TextureFormat.RED_INTEGER, + [TextureType.UNSIGNED_INT, 4], + 1, + true + ), + [TextureFormat.R8_SNORM]: $( + TextureFormat.RED, + [TextureType.BYTE, 1], + 1, + false, + true + ), + [TextureFormat.R8]: $( + TextureFormat.RED, + [TextureType.UNSIGNED_BYTE, 1], + 1, + true, + true + ), + [TextureFormat.R8I]: $( + TextureFormat.RED_INTEGER, + [TextureType.BYTE, 1], + 1, + true + ), + [TextureFormat.R8UI]: $( + TextureFormat.RED_INTEGER, + [TextureType.UNSIGNED_BYTE, 1], + 1, + true + ), + [TextureFormat.RG16F]: $( + TextureFormat.RG, + [TextureType.FLOAT, 8, TextureType.HALF_FLOAT, 4], + 2, + false, + true, + true + ), + [TextureFormat.RG16I]: $( + TextureFormat.RG_INTEGER, + [TextureType.SHORT, 4], + 2, + true + ), + [TextureFormat.RG16UI]: $( + TextureFormat.RG_INTEGER, + [TextureType.UNSIGNED_SHORT, 4], + 2, + true + ), + [TextureFormat.RG32F]: $( + TextureFormat.RG, + [TextureType.FLOAT, 8], + 2, + false, + false, + true + ), + [TextureFormat.RG32I]: $( + TextureFormat.RG_INTEGER, + [TextureType.INT, 8], + 2, + true + ), + [TextureFormat.RG32UI]: $( + TextureFormat.RG_INTEGER, + [TextureType.UNSIGNED_INT, 8], + 2, + true + ), + [TextureFormat.RG8_SNORM]: $( + TextureFormat.RG, + [TextureType.BYTE, 2], + 2, + false, + true + ), + [TextureFormat.RG8]: $( + TextureFormat.RG, + [TextureType.UNSIGNED_BYTE, 2], + 2, + true, + true + ), + [TextureFormat.RG8I]: $( + TextureFormat.RG_INTEGER, + [TextureType.BYTE, 2], + 2, + true + ), + [TextureFormat.RG8UI]: $( + TextureFormat.RG_INTEGER, + [TextureType.UNSIGNED_BYTE, 2], + 2, + true + ), + [TextureFormat.RGB]: $( + TextureFormat.RGB, + [ + TextureType.UNSIGNED_BYTE, + 3, + TextureType.HALF_FLOAT, + 6, + TextureType.HALF_FLOAT_OES, + 6, + TextureType.FLOAT, + 12, + TextureType.UNSIGNED_SHORT_5_6_5, + 2 + ], + 3, + true, + true + ), + [TextureFormat.RGB10_A2]: $( + TextureFormat.RGBA, + [TextureType.UNSIGNED_INT_2_10_10_10_REV, 4], + 4, + true, + true + ), + [TextureFormat.RGB10_A2UI]: $( + TextureFormat.RGBA_INTEGER, + [TextureType.UNSIGNED_INT_2_10_10_10_REV, 4], + 4, + true + ), + [TextureFormat.RGB16F]: $( + TextureFormat.RGB, + [TextureType.FLOAT, 12, TextureType.HALF_FLOAT, 6], + 3, + false, + true + ), + [TextureFormat.RGB16I]: $( + TextureFormat.RGB_INTEGER, + [TextureType.SHORT, 6], + 3 + ), + [TextureFormat.RGB16UI]: $( + TextureFormat.RGB_INTEGER, + [TextureType.UNSIGNED_SHORT, 6], + 3 + ), + [TextureFormat.RGB32F]: $(TextureFormat.RGB, [TextureType.FLOAT, 12], 3), + [TextureFormat.RGB32I]: $( + TextureFormat.RGB_INTEGER, + [TextureType.INT, 12], + 3 + ), + [TextureFormat.RGB32UI]: $( + TextureFormat.RGB_INTEGER, + [TextureType.UNSIGNED_INT, 12], + 3 + ), + [TextureFormat.RGB5_A1]: $( + TextureFormat.RGBA, + [ + TextureType.UNSIGNED_BYTE, + 4, + TextureType.UNSIGNED_SHORT_5_5_5_1, + 2, + TextureType.UNSIGNED_INT_2_10_10_10_REV, + 4 + ], + 4, + true, + true + ), + [TextureFormat.RGB565]: $( + TextureFormat.RGB, + [TextureType.UNSIGNED_BYTE, 3, TextureType.UNSIGNED_SHORT_5_6_5, 2], + 3, + true, + true + ), + [TextureFormat.RGB8_SNORM]: $( + TextureFormat.RGB, + [TextureType.BYTE, 3], + 3, + false, + true + ), + [TextureFormat.RGB8]: $( + TextureFormat.RGB, + [TextureType.UNSIGNED_BYTE, 3], + 3, + true, + true + ), + [TextureFormat.RGB8I]: $( + TextureFormat.RGB_INTEGER, + [TextureType.BYTE, 3], + 3 + ), + [TextureFormat.RGB8UI]: $( + TextureFormat.RGB_INTEGER, + [TextureType.UNSIGNED_BYTE, 3], + 3 + ), + [TextureFormat.RGB9_E5]: $( + TextureFormat.RGB, + [ + TextureType.FLOAT, + 12, + TextureType.HALF_FLOAT, + 6, + TextureType.UNSIGNED_INT_5_9_9_9_REV, + 4 + ], + 3, + false, + true + ), + [TextureFormat.RGBA]: $( + TextureFormat.RGBA, + [ + TextureType.UNSIGNED_BYTE, + 4, + TextureType.HALF_FLOAT, + 8, + TextureType.HALF_FLOAT_OES, + 8, + TextureType.FLOAT, + 16, + TextureType.UNSIGNED_SHORT_4_4_4_4, + 2, + TextureType.UNSIGNED_SHORT_5_5_5_1, + 2 + ], + 4, + true, + true + ), + [TextureFormat.RGBA16F]: $( + TextureFormat.RGBA, + [TextureType.FLOAT, 16, TextureType.HALF_FLOAT, 8], + 4, + false, + true, + true + ), + [TextureFormat.RGBA16I]: $( + TextureFormat.RGBA_INTEGER, + [TextureType.SHORT, 8], + 4, + true + ), + [TextureFormat.RGBA16UI]: $( + TextureFormat.RGBA_INTEGER, + [TextureType.UNSIGNED_SHORT, 8], + 4, + true + ), + [TextureFormat.RGBA32F]: $( + TextureFormat.RGBA, + [TextureType.FLOAT, 16], + 4, + false, + false, + true + ), + [TextureFormat.RGBA32I]: $( + TextureFormat.RGBA_INTEGER, + [TextureType.INT, 16], + 4, + true + ), + [TextureFormat.RGBA32UI]: $( + TextureFormat.RGBA_INTEGER, + [TextureType.UNSIGNED_INT, 16], + 4, + true + ), + [TextureFormat.RGBA4]: $( + TextureFormat.RGBA, + [TextureType.UNSIGNED_BYTE, 4, TextureType.UNSIGNED_SHORT_4_4_4_4, 2], + 4, + true, + true + ), + [TextureFormat.RGBA8_SNORM]: $( + TextureFormat.RGBA, + [TextureType.BYTE, 4], + 4, + false, + true + ), + [TextureFormat.RGBA8]: $( + TextureFormat.RGBA, + [TextureType.UNSIGNED_BYTE, 4], + 4, + true, + true + ), + [TextureFormat.RGBA8I]: $( + TextureFormat.RGBA_INTEGER, + [TextureType.BYTE, 4], + 4, + true + ), + [TextureFormat.RGBA8UI]: $( + TextureFormat.RGBA_INTEGER, + [TextureType.UNSIGNED_BYTE, 4], + 4, + true + ), + [TextureFormat.SRGB8_ALPHA8]: $( + TextureFormat.RGBA, + [TextureType.UNSIGNED_BYTE, 4], + 4, + true, + true + ), + [TextureFormat.SRGB8]: $( + TextureFormat.RGB, + [TextureType.UNSIGNED_BYTE, 3], + 3, + false, + true + ) +}; + +export type ReadableTextureFormat = + | TextureFormat.ALPHA + | TextureFormat.RED + | TextureFormat.RG + | TextureFormat.RGB + | TextureFormat.RGBA + | TextureFormat.RED_INTEGER + | TextureFormat.RG_INTEGER + | TextureFormat.RGB_INTEGER + | TextureFormat.RGBA_INTEGER; + +// export const RENDERABLE_TEX_FORMATS = Object.keys(TEX_FORMATS).filter( +// (id) => TEX_FORMATS[id].render! +// ); + +// export const FILTERABLE_TEX_FORMATS = Object.keys(TEX_FORMATS).filter( +// (id) => TEX_FORMATS[id].filter! +// ); + +export interface TextureOpts { + image: ArrayBufferView | TexImageSource | null; + target: TextureTarget; + type: TextureType; + filter: TextureFilter | [TextureFilter, TextureFilter?]; + wrap: TextureRepeat | [TextureRepeat, TextureRepeat?, TextureRepeat?]; + lod: [number, number?]; + minMaxLevel: [number, number]; + level: number; + format: TextureFormat; + width: number; + height: number; + depth: number; + mipmap: boolean; + flip: boolean; + premultiply: boolean; + sub: boolean; + pos: number[]; +} + +export interface ITexture + extends IBind, + IConfigure>, + IRelease { + tex: WebGLTexture; + target: TextureTarget; + format: TextureFormat; + type: TextureType; + size: number[]; +} diff --git a/packages/webgl/src/buffer.ts b/packages/webgl/src/buffer.ts index 336362bcfa..be9b5f9621 100644 --- a/packages/webgl/src/buffer.ts +++ b/packages/webgl/src/buffer.ts @@ -1,13 +1,9 @@ import { TypedArray } from "@thi.ng/api"; import { AttribPool } from "@thi.ng/vector-pools"; -import { - IndexBufferSpec, - IWebGLBuffer, - ModelAttributeSpecs, - ModelSpec -} from "./api"; +import { IndexBufferSpec, IWebGLBuffer } from "./api/buffers"; +import { ModelAttributeSpecs, ModelSpec } from "./api/model"; +import { isGL2Context } from "./checks"; import { error } from "./error"; -import { isGL2Context } from "./utils"; export class WebGLArrayBuffer implements IWebGLBuffer { gl: WebGLRenderingContext; diff --git a/packages/webgl/src/canvas.ts b/packages/webgl/src/canvas.ts index 46d5ebb7bb..aeda69e730 100644 --- a/packages/webgl/src/canvas.ts +++ b/packages/webgl/src/canvas.ts @@ -1,5 +1,6 @@ import { isString } from "@thi.ng/checks"; -import { WebGLExtensionMap, WeblGLCanvasOpts } from "./api"; +import { WeblGLCanvasOpts } from "./api/canvas"; +import { WebGLExtensionMap } from "./api/ext"; import { error } from "./error"; const defaultOpts: WebGLContextAttributes = { diff --git a/packages/webgl/src/checks.ts b/packages/webgl/src/checks.ts new file mode 100644 index 0000000000..a6c812f6c6 --- /dev/null +++ b/packages/webgl/src/checks.ts @@ -0,0 +1,13 @@ +import { ITexture, TextureType } from "./api/texture"; + +export const isGL2Context = ( + gl: WebGLRenderingContext | WebGL2RenderingContext +): gl is WebGL2RenderingContext => + typeof WebGL2RenderingContext !== "undefined" && + gl instanceof WebGL2RenderingContext; + +export const isFloatTexture = (tex: ITexture) => + tex.type === TextureType.FLOAT || + tex.type === TextureType.HALF_FLOAT || + tex.type === TextureType.HALF_FLOAT_OES || + tex.type === TextureType.FLOAT_32_UNSIGNED_INT_24_8_REV; diff --git a/packages/webgl/src/draw.ts b/packages/webgl/src/draw.ts index 231ae20945..6b005adfba 100644 --- a/packages/webgl/src/draw.ts +++ b/packages/webgl/src/draw.ts @@ -1,8 +1,8 @@ import { isArray } from "@thi.ng/checks"; -import { ModelSpec } from "./api"; +import { ModelSpec } from "./api/model"; +import { isGL2Context } from "./checks"; import { error } from "./error"; import { bindTextures } from "./texture"; -import { isGL2Context } from "./utils"; export const draw = (specs: ModelSpec | ModelSpec[]) => { const _specs = isArray(specs) ? specs : [specs]; diff --git a/packages/webgl/src/fbo.ts b/packages/webgl/src/fbo.ts index edd11bc728..f102a231bc 100644 --- a/packages/webgl/src/fbo.ts +++ b/packages/webgl/src/fbo.ts @@ -1,14 +1,12 @@ import { assert } from "@thi.ng/api"; -import { - FboOpts, - GL_COLOR_ATTACHMENT0_WEBGL, - GL_MAX_COLOR_ATTACHMENTS_WEBGL, - IFbo, - ITexture -} from "./api"; +import { FboOpts, IFbo } from "./api/buffers"; +import { ITexture, TEX_FORMATS } from "./api/texture"; +import { isGL2Context } from "./checks"; import { error } from "./error"; import { RBO } from "./rbo"; -import { isGL2Context } from "./utils"; + +const GL_COLOR_ATTACHMENT0_WEBGL = 0x8ce0; +const GL_MAX_COLOR_ATTACHMENTS_WEBGL = 0x8cdf; /** * WebGL framebuffer wrapper w/ automatic detection & support for @@ -34,10 +32,11 @@ export class FBO implements IFbo { constructor(gl: WebGLRenderingContext, opts?: Partial) { this.gl = gl; this.fbo = gl.createFramebuffer() || error("error creating FBO"); - this.ext = (!isGL2Context(gl) && opts && opts!.tex && opts!.tex!.length > 1) - ? gl.getExtension("WEBGL_draw_buffers") || - error("missing WEBGL_draw_buffers ext") - : undefined; + this.ext = + !isGL2Context(gl) && opts && opts!.tex && opts!.tex!.length > 1 + ? gl.getExtension("WEBGL_draw_buffers") || + error("missing WEBGL_draw_buffers ext") + : undefined; this.maxAttachments = gl.getParameter(GL_MAX_COLOR_ATTACHMENTS_WEBGL); opts && this.configure(opts); } @@ -69,12 +68,20 @@ export class FBO implements IFbo { ); const attachments: number[] = []; for (let i = 0; i < opts.tex.length; i++) { + const tex = opts.tex[i]; + assert( + !!( + TEX_FORMATS[tex.format].render || + TEX_FORMATS[tex.format].renderExt + ), + `texture #${i} has non-renderable format` + ); const attach = GL_COLOR_ATTACHMENT0_WEBGL + i; gl.framebufferTexture2D( gl.FRAMEBUFFER, attach, gl.TEXTURE_2D, - opts.tex[i].tex, + tex.tex, 0 ); attachments[i] = attach; diff --git a/packages/webgl/src/geo/cube.ts b/packages/webgl/src/geo/cube.ts index 9fe00b1bc7..365c3d1b16 100644 --- a/packages/webgl/src/geo/cube.ts +++ b/packages/webgl/src/geo/cube.ts @@ -1,4 +1,4 @@ -import { ModelSpec } from "../api"; +import { ModelSpec } from "../api/model"; export interface CubeOpts { size: number; diff --git a/packages/webgl/src/geo/quad.ts b/packages/webgl/src/geo/quad.ts index bae6ee1423..245731247f 100644 --- a/packages/webgl/src/geo/quad.ts +++ b/packages/webgl/src/geo/quad.ts @@ -1,4 +1,4 @@ -import { ModelSpec } from "../api"; +import { ModelSpec } from "../api/model"; export const quad = (uv = true): ModelSpec => ({ attribs: { diff --git a/packages/webgl/src/gpgpu.ts b/packages/webgl/src/gpgpu.ts deleted file mode 100644 index 20bb105ce0..0000000000 --- a/packages/webgl/src/gpgpu.ts +++ /dev/null @@ -1,282 +0,0 @@ -import { assert, IRelease } from "@thi.ng/api"; -import { mergeDeepObj } from "@thi.ng/associative"; -import { ceilPow2 } from "@thi.ng/binary"; -import { isNumber, isTypedArray } from "@thi.ng/checks"; -import { illegalArgs } from "@thi.ng/errors"; -import { - assocObj, - map, - range, - transduce -} from "@thi.ng/transducers"; -import { - GL_RGBA, - GL_RGBA32F, - GPGPUJobConfig, - GPGPUJobExecOpts, - GPGPUOpts, - GPGPUTextureConfig, - ITexture, - ModelSpec, - ShaderSpec -} from "./api"; -import { compileModel } from "./buffer"; -import { getExtensions, glCanvas } from "./canvas"; -import { draw } from "./draw"; -import { FBO } from "./fbo"; -import { quad } from "./geo/quad"; -import { FX_SHADER_SPEC } from "./pipeline"; -import { shader } from "./shader"; -import { floatTexture, texture } from "./texture"; -import { isGL2Context } from "./utils"; - -export const gpgpu = (opts: GPGPUOpts) => new GPGPU(opts); - -interface GPGPUIO { - tex: ITexture; - opts: GPGPUTextureConfig; -} - -export class GPGPU implements IRelease { - canvas?: HTMLCanvasElement; - gl: WebGLRenderingContext; - fbo: FBO; - inputs: GPGPUIO[]; - outputs: GPGPUIO[]; - spec: ModelSpec; - opts: GPGPUOpts; - width: number; - size: number; - - constructor(opts: GPGPUOpts) { - opts = { version: 1, inputs: 1, outputs: 1, ...opts }; - const width = ceilPow2(Math.ceil(Math.sqrt(opts.size / 4))); - let gl; - if (opts.gl) { - gl = opts.gl; - opts.version = isGL2Context(gl) ? 2 : 1; - } else { - const res = glCanvas({ - opts: { antialias: false, alpha: false, depth: false }, - width: 1, - height: 1, - autoScale: false, - version: opts.version - }); - this.canvas = res.canvas; - gl = res.gl; - } - getExtensions( - gl, - opts.version === 1 - ? ["WEBGL_color_buffer_float", "OES_texture_float"] - : ["EXT_color_buffer_float"] - ); - this.gl = gl; - this.opts = opts; - this.width = width; - this.size = width * width; - // let tmp = new Float32Array(this.size); - this.inputs = this.initTextures(opts.inputs!); - this.outputs = this.initTextures(opts.outputs!); - // tmp = null; - this.fbo = new FBO(gl); - this.spec = compileModel(gl, { - ...quad(), - textures: this.inputs.map((t) => t.tex) - }); - } - - inputSize(id: number) { - return this.inputs[id].opts.stride * this.size; - } - - outputSize(id: number) { - return this.outputs[id].opts.stride * this.size; - } - - newJob(opts: Partial) { - return new GPGPUJob(this, opts); - } - - release() { - this.fbo.release(); - for (let t of this.inputs) { - t.tex.release(); - } - for (let t of this.outputs) { - t.tex.release(); - } - delete this.inputs; - delete this.outputs; - delete this.spec; - delete this.canvas; - delete this.gl; - delete this.fbo; - delete this.opts; - return true; - } - - protected initTextures(specs: number | GPGPUTextureConfig[]) { - const gl = this.gl; - const width = this.width; - if (isNumber(specs)) { - return [ - ...map( - () => - { - tex: floatTexture(gl, null, width, width), - opts: { stride: 4 } - }, - range(specs) - ) - ]; - } else { - return specs.map( - (opts) => - { - tex: texture(gl, { - image: null, - width: width, - height: width, - filter: gl.NEAREST, - wrap: gl.CLAMP_TO_EDGE, - ...opts - }), - opts - } - ); - } - } -} - -export class GPGPUJob implements IRelease { - ctx: GPGPU; - spec: ModelSpec; - opts: GPGPUJobConfig; - - constructor(ctx: GPGPU, opts: Partial) { - opts = { - inputs: 1, - outputs: 1, - ...opts - }; - assert( - opts.inputs! <= ctx.opts.inputs!, - `context only supports max. ${ctx.opts.inputs} inputs` - ); - assert( - opts.outputs! <= ctx.opts.outputs!, - `context only supports max. ${ctx.opts.outputs} outputs` - ); - this.ctx = ctx; - this.opts = opts; - this.spec = this.buildSpec(); - } - - run(runOpts: GPGPUJobExecOpts) { - const inputs = runOpts.inputs; - const outputs = runOpts.outputs || [...range(this.opts.outputs!)]; - assert(inputs.length <= this.opts.inputs, "too many inputs"); - assert(outputs.length <= this.opts.outputs!, "too many outputs"); - const ctx = this.ctx; - const gl = ctx.gl; - const width = ctx.width; - const internalFormat = ctx.opts.version === 2 ? GL_RGBA32F : GL_RGBA; - const spec = this.spec; - for (let i = 0; i < inputs.length; i++) { - let tex = inputs[i]; - if (isTypedArray(tex)) { - const expectedSize = ctx.inputSize(i); - assert( - tex.length >= expectedSize, - `input #${i} too small (got ${ - tex.length - }, expected ${expectedSize})` - ); - const input = ctx.inputs[i]; - input.tex.configure({ - image: tex, - type: input.opts.type || gl.FLOAT, - internalFormat: input.opts.internalFormat || internalFormat, - format: input.opts.format || GL_RGBA, - filter: gl.NEAREST, - wrap: gl.CLAMP_TO_EDGE, - height: width, - width - }); - tex = input.tex; - } - spec.textures![i] = tex; - } - spec.uniforms = { ...spec.uniforms, ...runOpts.uniforms }; - ctx.fbo.configure({ tex: outputs.map((i) => ctx.outputs[i].tex) }); - gl.viewport(0, 0, width, width); - draw(spec); - return this; - } - - result(out?: Float32Array | null, id = 0) { - const ctx = this.ctx; - const width = ctx.width; - const gl = ctx.gl; - const output = ctx.outputs[id]; - const opts = output.opts; - const fbo = new FBO(gl, { tex: [output.tex] }); - out = out || new Float32Array(ctx.outputSize(id)); - gl.readPixels( - 0, - 0, - width, - width, - opts.format || gl.RGBA, - opts.type || gl.FLOAT, - out - ); - fbo.release(); - return out; - } - - release() { - this.spec.shader.release(); - delete this.spec; - delete this.ctx; - return true; - } - - protected buildSpec() { - const opts = this.opts; - const ctx = this.ctx; - const spec: ModelSpec = mergeDeepObj({}, ctx.spec); - let shaderSpec: ShaderSpec; - if (opts.src) { - shaderSpec = { - ...FX_SHADER_SPEC, - pre: `#define WIDTH (${ctx.width})\n#define SIZE (ivec2(${ - ctx.width - }))`, - fs: opts.src, - uniforms: { ...opts.uniforms }, - outputs: transduce( - map((i) => [`output${i}`, ["vec4", i]]), - assocObj(), - range(opts.outputs!) - ) - }; - } else if (opts.shader) { - shaderSpec = opts.shader; - shaderSpec.uniforms = shaderSpec.uniforms || {}; - shaderSpec.ext = shaderSpec.ext || {}; - } else { - return illegalArgs("require either `src` or `shader` option"); - } - shaderSpec.uniforms!.inputs = ["sampler2D[]", opts.inputs]; - if (ctx.opts.version === 1 && opts.outputs! > 1) { - shaderSpec.ext!.WEBGL_draw_buffers = "require"; - } - spec.uniforms!.inputs = [...range(opts.inputs)]; - spec.textures = ctx.inputs.slice(0, opts.inputs).map((t) => t.tex); - spec.shader = shader(ctx.gl, shaderSpec); - return spec; - } -} diff --git a/packages/webgl/src/index.ts b/packages/webgl/src/index.ts index 51f39e20c0..5745999a2d 100644 --- a/packages/webgl/src/index.ts +++ b/packages/webgl/src/index.ts @@ -1,14 +1,25 @@ -export * from "./api"; +export * from "./api/blend"; +export * from "./api/buffers"; +export * from "./api/canvas"; +export * from "./api/ext"; +export * from "./api/glsl"; +export * from "./api/logger"; +export * from "./api/material"; +export * from "./api/model"; +export * from "./api/shader"; +export * from "./api/texture"; + export * from "./buffer"; export * from "./canvas"; +export * from "./checks"; export * from "./draw"; export * from "./error"; export * from "./fbo"; -export * from "./gpgpu"; export * from "./material"; export * from "./matrices"; -export * from "./pipeline"; +export * from "./multipass"; export * from "./rbo"; +export * from "./readpixels"; export * from "./shader"; export * from "./syntax"; export * from "./texture"; @@ -16,6 +27,7 @@ export * from "./utils"; export * from "./shaders/lambert"; export * from "./shaders/phong"; +export * from "./shaders/pipeline"; export * from "./textures/checkerboard"; export * from "./textures/stripes"; diff --git a/packages/webgl/src/material.ts b/packages/webgl/src/material.ts index e09ac7a6d9..fe9065bc88 100644 --- a/packages/webgl/src/material.ts +++ b/packages/webgl/src/material.ts @@ -1,4 +1,6 @@ -import { GLSL, Material, ShaderUniformSpecs } from "./api"; +import { GLSL } from "./api/glsl"; +import { Material } from "./api/material"; +import { ShaderUniformSpecs } from "./api/shader"; export const DEFAULT_MATERIAL: Material = { ambientCol: [0.1, 0.1, 0.1], diff --git a/packages/webgl/src/matrices.ts b/packages/webgl/src/matrices.ts index 680c4a2788..879c97279a 100644 --- a/packages/webgl/src/matrices.ts +++ b/packages/webgl/src/matrices.ts @@ -7,7 +7,8 @@ import { ortho } from "@thi.ng/matrices"; import { ReadonlyVec } from "@thi.ng/vectors"; -import { GLMat4, ShaderUniforms } from "./api"; +import { GLMat4 } from "./api/glsl"; +import { ShaderUniforms } from "./api/shader"; const $ = (a: any, b: any, id: string) => a[id] || b[id].defaultVal || IDENT44; diff --git a/packages/webgl/src/multipass.ts b/packages/webgl/src/multipass.ts new file mode 100644 index 0000000000..f26aa411b3 --- /dev/null +++ b/packages/webgl/src/multipass.ts @@ -0,0 +1,164 @@ +import { assert, IObjectOf } from "@thi.ng/api"; +import { + assocObj, + map, + range, + some, + transduce +} from "@thi.ng/transducers"; +import { ExtensionBehaviors } from "./api/ext"; +import { Multipass, MultipassOpts, PassOpts } from "./api/multipass"; +import { ShaderOutputSpec, ShaderSpec, ShaderUniformSpecs } from "./api/shader"; +import { ITexture } from "./api/texture"; +import { compileModel } from "./buffer"; +import { isFloatTexture, isGL2Context } from "./checks"; +import { draw } from "./draw"; +import { fbo } from "./fbo"; +import { quad } from "./geo/quad"; +import { shader } from "./shader"; +import { PASSTHROUGH_VS } from "./shaders/pipeline"; +import { texture } from "./texture"; + +export const multipass = (opts: MultipassOpts) => { + const gl = opts.gl; + const isGL2 = isGL2Context(gl); + const numPasses = opts.passes.length; + assert(numPasses > 0, "require at least one shader pass"); + + const initShader = (pass: PassOpts) => { + const numIns = pass.inputs.length; + const numOuts = pass.outputs.length; + const ext: ExtensionBehaviors = {}; + const spec: ShaderSpec = { + vs: pass.vs || PASSTHROUGH_VS, + fs: pass.fs, + attribs: pass.attribs || { + position: "vec2" + }, + varying: pass.varying, + uniforms: { + ...pass.uniforms, + ...(numIns + ? { + inputs: ["sampler2D[]", numIns, [...range(numIns)]] + } + : null) + }, + outputs: numOuts + ? transduce( + map((i) => [ + `output${i}`, + ["vec4", i] + ]), + assocObj(), + range(numOuts) + ) + : undefined, + state: pass.state, + pre: pass.pre, + post: pass.post, + replacePrelude: pass.replacePrelude, + generateDecls: pass.generateDecls, + ext + }; + const floatIn = some((id) => isFloatTexture(textures[id]), pass.inputs); + const floatOut = some( + (id) => isFloatTexture(textures[id]), + pass.outputs + ); + if (!isGL2) { + floatIn && (ext.OES_texture_float = "require"); + numOuts > 1 && (ext.WEBGL_draw_buffers = "require"); + } + if (floatOut) { + ext[isGL2 ? "EXT_color_buffer_float" : "WEBGL_color_buffer_float"] = + "require"; + } + return shader(gl, spec); + }; + + const textures = Object.keys(opts.textures).reduce( + (acc, id) => { + acc[id] = texture(gl, { + width: opts.width, + height: opts.height, + filter: gl.NEAREST, + wrap: gl.CLAMP_TO_EDGE, + image: null, + ...opts.textures[id] + }); + return acc; + }, + >{} + ); + + const model = compileModel(gl, quad(false)); + const models = opts.passes.map((pass) => { + const m = pass.model ? compileModel(gl, pass.model) : { ...model }; + m.shader = initShader(pass); + m.uniforms = { ...pass.uniformVals }; + pass.inputs.length > 0 && + (m.textures = pass.inputs.map((id) => textures[id])); + return m; + }); + + const useMainBuffer = !opts.passes[numPasses - 1].outputs.length; + const fbos = (useMainBuffer + ? opts.passes.slice(0, numPasses - 1) + : opts.passes + ).map((pass) => fbo(gl, { tex: pass.outputs.map((id) => textures[id]) })); + + const drawPass = (i: number, time: number) => { + const pass = opts.passes[i]; + const model = models[i]; + const shader = model.shader; + const size = pass.outputs.length + ? textures[pass.outputs[0]].size + : [gl.drawingBufferWidth, gl.drawingBufferHeight]; + shader.uniforms.resolution && (model.uniforms!.resolution = size); + shader.uniforms.time && (model.uniforms!.time = time); + gl.viewport(0, 0, size[0], size[1]); + draw(model); + }; + + const update = (time: number) => { + for (let i = 0; i < fbos.length; i++) { + fbos[i].bind(); + drawPass(i, time); + fbos[i].unbind(); + } + useMainBuffer && drawPass(numPasses - 1, time); + }; + + const updateRAF = () => { + update((Date.now() - t0) * 1e-3); + active && (rafID = requestAnimationFrame(updateRAF)); + }; + + let active: boolean; + let t0 = Date.now(); + let rafID: number; + + const instance: Multipass = { + start() { + t0 = Date.now(); + active = true; + rafID = requestAnimationFrame(updateRAF); + }, + stop() { + if (active) { + active = false; + cancelAnimationFrame(rafID); + } + }, + update(time: number) { + update(time); + }, + passes: opts.passes, + fbos, + models, + textures + }; + + return instance; +}; diff --git a/packages/webgl/src/pipeline.ts b/packages/webgl/src/pipeline.ts deleted file mode 100644 index 2de1748f84..0000000000 --- a/packages/webgl/src/pipeline.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - assign, - defMain, - FLOAT0, - FLOAT1, - texture, - vec4 -} from "@thi.ng/shader-ast"; -import { ShaderFn, ShaderSpec } from "./api"; - -export const PASSTHROUGH_VS: ShaderFn = (gl, _, ins, outs) => [ - defMain(() => [ - assign(outs.v_uv, ins.uv), - assign(gl.gl_Position, vec4(ins.position, FLOAT0, FLOAT1)) - ]) -]; - -export const PASSTHROUGH_FS: ShaderFn = (_, unis, ins, outs) => [ - defMain(() => [assign(outs.fragColor, texture(unis.tex, ins.v_uv))]) -]; - -export const FX_SHADER_SPEC: ShaderSpec = { - vs: PASSTHROUGH_VS, - fs: PASSTHROUGH_FS, - attribs: { position: "vec2", uv: "vec2" }, - varying: { v_uv: "vec2" }, - uniforms: { tex: "sampler2D" }, - state: { depth: false }, - ext: {} -}; diff --git a/packages/webgl/src/rbo.ts b/packages/webgl/src/rbo.ts index 2cb991afba..e20951abed 100644 --- a/packages/webgl/src/rbo.ts +++ b/packages/webgl/src/rbo.ts @@ -1,4 +1,4 @@ -import { IRenderBuffer, RboOpts } from "./api"; +import { IRenderBuffer, RboOpts } from "./api/buffers"; import { error } from "./error"; export class RBO implements IRenderBuffer { diff --git a/packages/webgl/src/readpixels.ts b/packages/webgl/src/readpixels.ts new file mode 100644 index 0000000000..4edfd2dfdf --- /dev/null +++ b/packages/webgl/src/readpixels.ts @@ -0,0 +1,34 @@ +import { ITexture, ReadableTextureFormat, TextureType } from "./api/texture"; +import { FBO } from "./fbo"; + +export const readPixels = < + T extends Uint8Array | Uint16Array | Uint32Array | Float32Array +>( + gl: WebGLRenderingContext, + x: number, + y: number, + w: number, + h: number, + format: ReadableTextureFormat, + type: TextureType, + out: T +) => { + gl.readPixels(x, y, w, h, format, type, out); + return out; +}; + +export const readTexture = < + T extends Uint8Array | Uint16Array | Uint32Array | Float32Array +>( + gl: WebGLRenderingContext, + tex: ITexture, + format: ReadableTextureFormat, + type: TextureType, + out: T +) => { + const fbo = new FBO(gl, { tex: [tex] }); + gl.readPixels(0, 0, tex.size[0], tex.size[1], format, type, out); + fbo.unbind(); + fbo.release(); + return out; +}; diff --git a/packages/webgl/src/shader.ts b/packages/webgl/src/shader.ts index 81f8c6aed4..e785e2e971 100644 --- a/packages/webgl/src/shader.ts +++ b/packages/webgl/src/shader.ts @@ -17,15 +17,19 @@ import { } from "@thi.ng/shader-ast"; import { GLSLVersion, targetGLSL } from "@thi.ng/shader-ast-glsl"; import { vals } from "@thi.ng/transducers"; +import { + ExtensionBehavior, + ExtensionBehaviors, + ExtensionName, + GL_EXT_INFO +} from "./api/ext"; +import { GLSL } from "./api/glsl"; +import { LOGGER } from "./api/logger"; +import { ModelAttributeSpecs, ModelSpec } from "./api/model"; import { DEFAULT_OUTPUT, - GL_EXT_INFO, - GLSL, GLSLDeclPrefixes, - GLSLExtensionBehavior, IShader, - ModelAttributeSpecs, - ModelSpec, ShaderAttrib, ShaderAttribSpecs, ShaderFn, @@ -37,12 +41,12 @@ import { ShaderUniformSpecs, UniformValue, UniformValues -} from "./api"; +} from "./api/shader"; import { getExtensions } from "./canvas"; +import { isGL2Context } from "./checks"; import { error } from "./error"; import { GLSL_HEADER, NO_PREFIXES, SYNTAX } from "./syntax"; import { UNIFORM_SETTERS } from "./uniforms"; -import { isGL2Context } from "./utils"; const ERROR_REGEXP = /ERROR: \d+:(\d+): (.*)/; @@ -233,13 +237,13 @@ const compileVars = ( const compileExtensionPragma = ( id: string, - behavior: GLSLExtensionBehavior, + behavior: ExtensionBehavior, version: GLSLVersion ) => { - const ext = (GL_EXT_INFO)[id]; + const ext = GL_EXT_INFO[id]; const gl2 = version === GLSLVersion.GLES_300; - return !ext || (!gl2 && ext.gl) || (gl2 && ext.gl2) - ? `#extension ${ext.alias || id} : ${ + return ext && ((!gl2 && ext.gl) || (gl2 && ext.gl2)) + ? `#extension ${(ext && ext.alias) || id} : ${ isBoolean(behavior) ? (behavior ? "enable" : "disable") : behavior }\n` : ""; @@ -247,11 +251,11 @@ const compileExtensionPragma = ( const initShaderExtensions = ( gl: WebGLRenderingContext, - exts: IObjectOf | undefined + exts: ExtensionBehaviors | undefined ) => { if (exts) { for (let id in exts) { - const state = exts[id]; + const state = exts[id]; if (state === true || state === "require") { getExtensions(gl, [id], state === "require"); } @@ -272,7 +276,11 @@ export const shaderSourceFromAST = ( : GLSL_HEADER; if (spec.ext) { for (let id in spec.ext) { - prelude += compileExtensionPragma(id, spec.ext[id], version); + prelude += compileExtensionPragma( + id, + spec.ext[id]!, + version + ); } } const inputs: IObjectOf> = {}; @@ -370,7 +378,11 @@ export const prepareShaderSource = ( : GLSL_HEADER; if (spec.ext) { for (let id in spec.ext) { - src += compileExtensionPragma(id, spec.ext[id], version); + src += compileExtensionPragma( + id, + spec.ext[id]!, + version + ); } } if (spec.generateDecls !== false) { @@ -467,18 +479,21 @@ const initUniforms = ( type = val; } const loc = gl.getUniformLocation(prog, id)!; - loc == null && error(`unknown uniform: ${id}`); - const setter = UNIFORM_SETTERS[type]; - if (setter) { - res[id] = { - loc, - setter: setter(gl, loc, defaultVal), - defaultFn, - defaultVal, - type - }; + if (loc != null) { + const setter = UNIFORM_SETTERS[type]; + if (setter) { + res[id] = { + loc, + setter: setter(gl, loc, defaultVal), + defaultFn, + defaultVal, + type + }; + } else { + error(`invalid uniform type: ${type}`); + } } else { - error(`invalid uniform type: ${type}`); + LOGGER.warn(`unknown uniform: ${id}`); } } return res; diff --git a/packages/webgl/src/shaders/lambert.ts b/packages/webgl/src/shaders/lambert.ts index 8982375ae9..4c6a09feaa 100644 --- a/packages/webgl/src/shaders/lambert.ts +++ b/packages/webgl/src/shaders/lambert.ts @@ -14,7 +14,8 @@ import { surfaceNormal, transformMVP } from "@thi.ng/shader-ast-stdlib"; -import { Material, ShaderOpts, ShaderSpec } from "../api"; +import { Material } from "../api/material"; +import { ShaderOpts, ShaderSpec } from "../api/shader"; import { defMaterial } from "../material"; import { autoNormalMatrix2 } from "../matrices"; import { colorAttrib, positionAttrib } from "../utils"; diff --git a/packages/webgl/src/shaders/phong.ts b/packages/webgl/src/shaders/phong.ts index 0d86d21fa8..fbf93a9875 100644 --- a/packages/webgl/src/shaders/phong.ts +++ b/packages/webgl/src/shaders/phong.ts @@ -17,7 +17,8 @@ import { vec4 } from "@thi.ng/shader-ast"; import { diffuseLighting, surfaceNormal } from "@thi.ng/shader-ast-stdlib"; -import { Material, ShaderOpts, ShaderSpec } from "../api"; +import { Material } from "../api/material"; +import { ShaderOpts, ShaderSpec } from "../api/shader"; import { defMaterial } from "../material"; import { autoNormalMatrix1 } from "../matrices"; import { colorAttrib, positionAttrib } from "../utils"; diff --git a/packages/webgl/src/shaders/pipeline.ts b/packages/webgl/src/shaders/pipeline.ts new file mode 100644 index 0000000000..87b6e996ca --- /dev/null +++ b/packages/webgl/src/shaders/pipeline.ts @@ -0,0 +1,49 @@ +import { + $xy, + assign, + defMain, + FLOAT0, + FLOAT1, + texture, + vec4 +} from "@thi.ng/shader-ast"; +import { ShaderFn, ShaderSpec } from "../api/shader"; + +export const PASSTHROUGH_VS: ShaderFn = (gl, _, ins) => [ + defMain(() => [assign(gl.gl_Position, vec4(ins.position, FLOAT0, FLOAT1))]) +]; + +export const PASSTHROUGH_VS_UV: ShaderFn = (gl, _, ins, outs) => [ + defMain(() => [ + assign(outs.v_uv, ins.uv), + assign(gl.gl_Position, vec4(ins.position, FLOAT0, FLOAT1)) + ]) +]; + +export const PASSTHROUGH_FS: ShaderFn = (gl, _, __, outs) => [ + defMain(() => [assign(outs.fragColor, $xy(gl.gl_FragCoord))]) +]; + +export const PASSTHROUGH_FS_UV: ShaderFn = (_, unis, ins, outs) => [ + defMain(() => [assign(outs.fragColor, texture(unis.tex, ins.v_uv))]) +]; + +export const FX_SHADER_SPEC: ShaderSpec = { + vs: PASSTHROUGH_VS, + fs: PASSTHROUGH_FS, + attribs: { position: "vec2" }, + varying: {}, + uniforms: {}, + state: { depth: false }, + ext: {} +}; + +export const FX_SHADER_SPEC_UV: ShaderSpec = { + vs: PASSTHROUGH_VS_UV, + fs: PASSTHROUGH_FS_UV, + attribs: { position: "vec2", uv: "vec2" }, + varying: { v_uv: "vec2" }, + uniforms: { tex: "sampler2D" }, + state: { depth: false }, + ext: {} +}; diff --git a/packages/webgl/src/syntax.ts b/packages/webgl/src/syntax.ts index b4ed10c53e..d6b3b81310 100644 --- a/packages/webgl/src/syntax.ts +++ b/packages/webgl/src/syntax.ts @@ -1,6 +1,7 @@ import { isArray } from "@thi.ng/checks"; import { GLSLVersion } from "@thi.ng/shader-ast-glsl"; -import { GLSL, GLSLDeclPrefixes, GLSLSyntax } from "./api"; +import { GLSL } from "./api/glsl"; +import { GLSLDeclPrefixes, GLSLSyntax } from "./api/shader"; export const PREFIXES: GLSLDeclPrefixes = { a: "a_", diff --git a/packages/webgl/src/texture.ts b/packages/webgl/src/texture.ts index e49611282a..9567c076c4 100644 --- a/packages/webgl/src/texture.ts +++ b/packages/webgl/src/texture.ts @@ -1,8 +1,15 @@ import { withoutKeysObj } from "@thi.ng/associative"; import { isArray } from "@thi.ng/checks"; -import { ITexture, TextureOpts } from "./api"; +import { + ITexture, + TEX_FORMATS, + TextureFormat, + TextureOpts, + TextureTarget, + TextureType +} from "./api/texture"; +import { isGL2Context } from "./checks"; import { error } from "./error"; -import { isGL2Context } from "./utils"; export const bindTextures = (textures: ITexture[]) => { if (!textures) return; @@ -14,23 +21,30 @@ export const bindTextures = (textures: ITexture[]) => { export class Texture implements ITexture { gl: WebGLRenderingContext; tex: WebGLTexture; - target: GLenum; + target!: TextureTarget; + format!: TextureFormat; + type!: TextureType; + size!: number[]; constructor(gl: WebGLRenderingContext, opts: Partial = {}) { this.gl = gl; this.tex = gl.createTexture() || error("error creating WebGL texture"); - this.target = opts!.target || gl.TEXTURE_2D; this.configure(opts); } configure(opts: Partial = {}) { const gl = this.gl; const isGL2 = isGL2Context(gl); - const target = this.target; - const imgTarget = opts.target || target; - const format = opts.format || gl.RGBA; - const internalFormat = opts.internalFormat || format; - const type = opts.type || gl.UNSIGNED_BYTE; + const target = opts.target || this.target || TextureTarget.TEXTURE_2D; + const format = opts.format || this.format || TextureFormat.RGBA; + const decl = TEX_FORMATS[format]; + const baseFormat = decl.format; + const type = opts.type || this.type || decl.types[0]; + + !this.target && (this.target = target); + this.format = format; + this.type = type; + let t1: GLenum, t2: GLenum, t3: GLenum; gl.bindTexture(this.target, this.tex); @@ -46,50 +60,94 @@ export class Texture implements ITexture { if (opts.image !== undefined) { const level = opts.level || 0; - const pos = opts.pos || [0, 0]; - if (opts.width && opts.height) { - opts.sub - ? gl.texSubImage2D( - imgTarget, - level, - pos[0], - pos[1], - opts.width, - opts.height, - format, - type, - opts.image - ) - : gl.texImage2D( - imgTarget, - level, - internalFormat, - opts.width, - opts.height, - 0, - format, - type, - opts.image - ); + const pos = opts.pos || [0, 0, 0]; + if (target === TextureTarget.TEXTURE_3D) { + if (opts.width && opts.height && opts.depth) { + if (opts.sub) { + (gl).texSubImage3D( + target, + level, + pos[0], + pos[1], + pos[2], + opts.width, + opts.height, + opts.depth, + baseFormat, + type, + opts.image + ); + } else { + if (level === 0) { + this.size = [opts.width, opts.height, opts.depth]; + } + (gl).texImage3D( + target, + level, + format, + opts.width, + opts.height, + opts.depth, + 0, + baseFormat, + type, + opts.image + ); + } + } } else { - opts.sub - ? gl.texSubImage2D( - imgTarget, - level, - pos[0], - pos[1], - format, - type, - opts.image - ) - : gl.texImage2D( - imgTarget, - level, - internalFormat, - format, - type, - opts.image - ); + if (opts.width && opts.height) { + if (opts.sub) { + gl.texSubImage2D( + target, + level, + pos[0], + pos[1], + opts.width, + opts.height, + baseFormat, + type, + opts.image + ); + } else { + if (level === 0) { + this.size = [opts.width, opts.height]; + } + gl.texImage2D( + target, + level, + format, + opts.width, + opts.height, + 0, + baseFormat, + type, + opts.image + ); + } + } else { + if (opts.sub) { + gl.texSubImage2D( + target, + level, + pos[0], + pos[1], + baseFormat, + type, + opts.image + ); + } else { + if (opts.image != null && level == 0) { + this.size = [ + (opts.image).width, + (opts.image).height + ]; + } + gl.texImage2D(target, level, format, baseFormat, type, < + TexImageSource + >opts.image); + } + } } } @@ -243,22 +301,24 @@ export const cubeMap = ( * @param data texture data * @param width width * @param height height + * @param format + * @param type */ export const floatTexture = ( gl: WebGLRenderingContext, data: Float32Array | undefined | null, width: number, height: number, - internalFormat?: GLenum, - format?: GLenum + format?: TextureFormat, + type?: TextureType ) => new Texture(gl, { filter: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, - internalFormat: - internalFormat || (isGL2Context(gl) ? gl.RGBA32F : gl.RGBA), - format: format || gl.RGBA, - type: gl.FLOAT, + format: + format || + (isGL2Context(gl) ? TextureFormat.RGBA32F : TextureFormat.RGBA), + type: type || gl.FLOAT, image: data, width, height diff --git a/packages/webgl/src/uniforms.ts b/packages/webgl/src/uniforms.ts index c80f8e1216..d847e31842 100644 --- a/packages/webgl/src/uniforms.ts +++ b/packages/webgl/src/uniforms.ts @@ -7,7 +7,8 @@ import { ZERO3, ZERO4 } from "@thi.ng/vectors"; -import { GLVec, UniformValue } from "./api"; +import { GLVec } from "./api/glsl"; +import { UniformValue } from "./api/shader"; type SetterS = "f" | "i" | "ui"; @@ -42,17 +43,17 @@ const uniformS = (fn: SetterS) => ( }; }; -const uniformV = (fn: SetterV, sysDefault?: ReadonlyVec) => ( +const uniformV = (fn: SetterV, sysDefault: ReadonlyVec) => ( gl: WebGLRenderingContext, loc: WebGLUniformLocation, defaultVal = sysDefault ) => { let prev: GLVec = []; - return (x?: any) => { + return (x?: ReadonlyVec) => { x = x === undefined ? defaultVal : x; if (!equivArrayLike(prev, x)) { (gl)["uniform" + fn](loc, x); - prev = x; + prev = [...x]; } }; }; @@ -67,11 +68,13 @@ const uniformM = (fn: SetterM, sysDefault?: ReadonlyVec) => ( x = x === undefined ? defaultVal : x; if (!equivArrayLike(prev, x)) { (gl)["uniformMatrix" + fn](loc, false, x); - prev = x; + prev = [...x]; } }; }; +const Z1 = [0]; + export const UNIFORM_SETTERS: IObjectOf< Fn3< WebGLRenderingContext, @@ -101,25 +104,25 @@ export const UNIFORM_SETTERS: IObjectOf< sampler3D: uniformS("i"), samplerCube: uniformS("i"), samplerCubeShadow: uniformS("i"), - "bool[]": uniformV("1iv"), - "float[]": uniformV("1fv"), - "int[]": uniformV("1iv"), - "uint[]": uniformV("1uiv"), - "bvec2[]": uniformV("2iv"), - "bvec3[]": uniformV("3iv"), - "bvec4[]": uniformV("4iv"), - "ivec2[]": uniformV("2iv"), - "ivec3[]": uniformV("3iv"), - "ivec4[]": uniformV("4iv"), - "vec2[]": uniformV("2fv"), - "vec3[]": uniformV("3fv"), - "vec4[]": uniformV("4fv"), - "mat2[]": uniformM("2fv"), - "mat3[]": uniformM("3fv"), - "mat4[]": uniformM("4fv"), - "sampler2D[]": uniformV("1iv"), - "sampler2DShadow[]": uniformV("1iv"), - "sampler3D[]": uniformV("1iv"), - "samplerCube[]": uniformV("1iv"), - "samplerCubeShadow[]": uniformV("1iv") + "bool[]": uniformV("1iv", Z1), + "float[]": uniformV("1fv", Z1), + "int[]": uniformV("1iv", Z1), + "uint[]": uniformV("1uiv", Z1), + "bvec2[]": uniformV("2iv", ZERO2), + "bvec3[]": uniformV("3iv", ZERO3), + "bvec4[]": uniformV("4iv", ZERO4), + "ivec2[]": uniformV("2iv", ZERO2), + "ivec3[]": uniformV("3iv", ZERO3), + "ivec4[]": uniformV("4iv", ZERO4), + "vec2[]": uniformV("2fv", ZERO2), + "vec3[]": uniformV("3fv", ZERO3), + "vec4[]": uniformV("4fv", ZERO4), + "mat2[]": uniformM("2fv", ZERO2), + "mat3[]": uniformM("3fv", ZERO3), + "mat4[]": uniformM("4fv", ZERO4), + "sampler2D[]": uniformV("1iv", Z1), + "sampler2DShadow[]": uniformV("1iv", Z1), + "sampler3D[]": uniformV("1iv", Z1), + "samplerCube[]": uniformV("1iv", Z1), + "samplerCubeShadow[]": uniformV("1iv", Z1) }; diff --git a/packages/webgl/src/utils.ts b/packages/webgl/src/utils.ts index 625522d3f5..3059f6e55c 100644 --- a/packages/webgl/src/utils.ts +++ b/packages/webgl/src/utils.ts @@ -5,13 +5,7 @@ import { Sym, Term } from "@thi.ng/shader-ast"; -import { ShaderOpts } from "./api"; - -export const isGL2Context = ( - gl: WebGLRenderingContext -): gl is WebGL2RenderingContext => - typeof WebGL2RenderingContext !== "undefined" && - gl instanceof WebGL2RenderingContext; +import { ShaderOpts } from "./api/shader"; export const positionAttrib = ( opts: Partial>, diff --git a/scripts/base64 b/scripts/base64 new file mode 100755 index 0000000000..fba97a12f4 --- /dev/null +++ b/scripts/base64 @@ -0,0 +1,18 @@ +#!/usr/bin/env node +const fs = require("fs"); +const tx = require("@thi.ng/transducers-binary"); + +const src = process.argv[2]; +if (!src || !src.length) { + process.stderr.write("missing input file"); + process.exit(1); +} + +try { + const raw = fs.readFileSync(src); + const encoded = tx.base64Encode(raw); + process.stdout.write(encoded); +} catch (e) { + process.stderr.write(`error encoding: ${e.message}`); + process.exit(1); +} diff --git a/scripts/make-module b/scripts/make-module index 2359b49794..efb566ccc9 100755 --- a/scripts/make-module +++ b/scripts/make-module @@ -62,11 +62,11 @@ cat << EOF > $MODULE/package.json "@types/node": "^12.6.3", "mocha": "^6.1.4", "nyc": "^14.0.0", - "typedoc": "^0.14.2", - "typescript": "^3.5.3" + "typedoc": "^0.15.0", + "typescript": "^3.6.4" }, "dependencies": { - "@thi.ng/api": "^6.3.1" + "@thi.ng/api": "^6.4.0" }, "keywords": [ "ES6", diff --git a/scripts/update-thing-links b/scripts/update-thing-links new file mode 100755 index 0000000000..e50a918fbb --- /dev/null +++ b/scripts/update-thing-links @@ -0,0 +1,31 @@ +#!/usr/bin/env node +const fs = require("fs"); +const getIn = require("@thi.ng/paths").getIn; +const execSync = require("child_process").execSync; + +const baseDir = "./packages/"; +const tmpFile = `./temp-${Date.now()}`; + +for (let f of fs.readdirSync(baseDir)) { + f = baseDir + f; + if (f.indexOf(".DS_Store") >= 0 || !fs.statSync(f).isDirectory) continue; + try { + const pkg = JSON.parse(fs.readFileSync(f + "/package.json")); + const id = pkg.name.split("/")[1]; + if (getIn(pkg, ["thi.ng", "shortlink"]) === false) { + console.log(`\tskipping: ${id}`); + continue; + } + const branch = getIn(pkg, ["thi.ng", "branch"]) || "master"; + const html = ``; + console.log(`${id} -> ${branch}`); + fs.writeFileSync(tmpFile, html); + execSync( + `aws s3 cp ${tmpFile} s3://thi.ng/${id} --profile toxi-s3 --acl public-read --content-type "text/html" --cache-control "no-cache"` + ); + } catch (e) { + console.warn(`error: ${e.message}`); + } +} + +fs.unlinkSync(tmpFile); diff --git a/yarn.lock b/yarn.lock index 441bf2b901..b94f61afd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,54 +9,35 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/code-frame@^7.0.0 <7.4.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" - integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== - dependencies: - "@babel/highlight" "^7.0.0" - -"@babel/core@^7.0.0 <7.4.0": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.4.tgz#921a5a13746c21e32445bf0798680e9d11a6530b" - integrity sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA== +"@babel/core@^7.4.4": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.4.tgz#6ebd9fe00925f6c3e177bb726a188b5f578088ff" + integrity sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ== dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.3.4" - "@babel/helpers" "^7.2.0" - "@babel/parser" "^7.3.4" - "@babel/template" "^7.2.2" - "@babel/traverse" "^7.3.4" - "@babel/types" "^7.3.4" + "@babel/code-frame" "^7.5.5" + "@babel/generator" "^7.6.4" + "@babel/helpers" "^7.6.2" + "@babel/parser" "^7.6.4" + "@babel/template" "^7.6.0" + "@babel/traverse" "^7.6.3" + "@babel/types" "^7.6.3" convert-source-map "^1.1.0" debug "^4.1.0" json5 "^2.1.0" - lodash "^4.17.11" + lodash "^4.17.13" resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.0.0 <7.4.0": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.4.tgz#9aa48c1989257877a9d971296e5b73bfe72e446e" - integrity sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg== +"@babel/generator@^7.4.0", "@babel/generator@^7.4.4", "@babel/generator@^7.6.3", "@babel/generator@^7.6.4": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.4.tgz#a4f8437287bf9671b07f483b76e3bb731bc97671" + integrity sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w== dependencies: - "@babel/types" "^7.3.4" - jsesc "^2.5.1" - lodash "^4.17.11" - source-map "^0.5.0" - trim-right "^1.0.1" - -"@babel/generator@^7.3.4", "@babel/generator@^7.4.0", "@babel/generator@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.5.5.tgz#873a7f936a3c89491b43536d12245b626664e3cf" - integrity sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ== - dependencies: - "@babel/types" "^7.5.5" + "@babel/types" "^7.6.3" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" - trim-right "^1.0.1" "@babel/helper-annotate-as-pure@^7.0.0": version "7.0.0" @@ -204,7 +185,7 @@ "@babel/template" "^7.1.0" "@babel/types" "^7.0.0" -"@babel/helper-split-export-declaration@^7.0.0", "@babel/helper-split-export-declaration@^7.4.4": +"@babel/helper-split-export-declaration@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== @@ -221,14 +202,14 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.2.0" -"@babel/helpers@^7.2.0": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.5.5.tgz#63908d2a73942229d1e6685bc2a0e730dde3b75e" - integrity sha512-nRq2BUhxZFnfEn/ciJuhklHvFOqjJUD5wpx+1bxUF2axL9C+v4DE/dmp5sT2dKnpOs4orZWzpAZqlCy8QqE/7g== +"@babel/helpers@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.6.2.tgz#681ffe489ea4dcc55f23ce469e58e59c1c045153" + integrity sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA== dependencies: - "@babel/template" "^7.4.4" - "@babel/traverse" "^7.5.5" - "@babel/types" "^7.5.5" + "@babel/template" "^7.6.0" + "@babel/traverse" "^7.6.2" + "@babel/types" "^7.6.0" "@babel/highlight@^7.0.0": version "7.5.0" @@ -239,15 +220,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0 <7.4.0": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c" - integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ== - -"@babel/parser@^7.2.2", "@babel/parser@^7.3.4", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b" - integrity sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g== +"@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.6.0", "@babel/parser@^7.6.3", "@babel/parser@^7.6.4": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81" + integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A== "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" @@ -258,6 +234,14 @@ "@babel/helper-remap-async-to-generator" "^7.1.0" "@babel/plugin-syntax-async-generators" "^7.2.0" +"@babel/plugin-proposal-dynamic-import@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz#e532202db4838723691b10a67b8ce509e397c506" + integrity sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-dynamic-import" "^7.2.0" + "@babel/plugin-proposal-json-strings@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317" @@ -266,10 +250,10 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-json-strings" "^7.2.0" -"@babel/plugin-proposal-object-rest-spread@^7.3.4": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz#61939744f71ba76a3ae46b5eea18a54c16d22e58" - integrity sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw== +"@babel/plugin-proposal-object-rest-spread@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz#8ffccc8f3a6545e9f78988b6bf4fe881b88e8096" + integrity sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" @@ -282,14 +266,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" -"@babel/plugin-proposal-unicode-property-regex@^7.2.0": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz#501ffd9826c0b91da22690720722ac7cb1ca9c78" - integrity sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA== +"@babel/plugin-proposal-unicode-property-regex@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz#05413762894f41bfe42b9a5e80919bd575dcc802" + integrity sha512-NxHETdmpeSCtiatMRYWVJo7266rrvAC3DTeG5exQBIH/fMIUK7ejDNznBbn3HQl/o9peymRRg7Yqkx6PdUXmMw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.5.4" + regexpu-core "^4.6.0" "@babel/plugin-syntax-async-generators@^7.2.0": version "7.2.0" @@ -298,6 +282,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-dynamic-import@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612" + integrity sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-flow@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.2.0.tgz#a765f061f803bc48f240c26f8747faf97c26bf7c" @@ -340,7 +331,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-async-to-generator@^7.3.4": +"@babel/plugin-transform-async-to-generator@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz#89a3848a0166623b5bc481164b5936ab947e887e" integrity sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg== @@ -356,15 +347,15 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-block-scoping@^7.3.4": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz#a35f395e5402822f10d2119f6f8e045e3639a2ce" - integrity sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg== +"@babel/plugin-transform-block-scoping@^7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz#6e854e51fbbaa84351b15d4ddafe342f3a5d542a" + integrity sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" lodash "^4.17.13" -"@babel/plugin-transform-classes@^7.3.4": +"@babel/plugin-transform-classes@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz#d094299d9bd680a14a2a0edae38305ad60fb4de9" integrity sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg== @@ -385,23 +376,23 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-destructuring@^7.2.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.5.0.tgz#f6c09fdfe3f94516ff074fe877db7bc9ef05855a" - integrity sha512-YbYgbd3TryYYLGyC7ZR+Tq8H/+bCmwoaxHfJHupom5ECstzbRLTch6gOQbhEY9Z4hiCNHEURgq06ykFv9JZ/QQ== +"@babel/plugin-transform-destructuring@^7.6.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz#44bbe08b57f4480094d57d9ffbcd96d309075ba6" + integrity sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-dotall-regex@^7.2.0": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz#361a148bc951444312c69446d76ed1ea8e4450c3" - integrity sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg== +"@babel/plugin-transform-dotall-regex@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.6.2.tgz#44abb948b88f0199a627024e1508acaf8dc9b2f9" + integrity sha512-KGKT9aqKV+9YMZSkowzYoYEiHqgaDhGmPNZlZxX6UeHC4z30nC1J9IrZuGqbYFB1jaIGdv91ujpze0exiVK8bA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.5.4" + regexpu-core "^4.6.0" -"@babel/plugin-transform-duplicate-keys@^7.2.0": +"@babel/plugin-transform-duplicate-keys@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz#c5dbf5106bf84cdf691222c0974c12b1df931853" integrity sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ== @@ -416,22 +407,22 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-flow-strip-types@^7.0.0 <7.4.0": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.3.4.tgz#00156236defb7dedddc2d3c9477dcc01a4494327" - integrity sha512-PmQC9R7DwpBFA+7ATKMyzViz3zCaMNouzZMPZN2K5PnbBbtL3AXFYTkDk+Hey5crQq2A90UG5Uthz0mel+XZrA== +"@babel/plugin-transform-flow-strip-types@^7.4.4": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.6.3.tgz#8110f153e7360cfd5996eee68706cfad92d85256" + integrity sha512-l0ETkyEofkqFJ9LS6HChNIKtVJw2ylKbhYMlJ5C6df+ldxxaLIyXY4yOdDQQspfFpV8/vDiaWoJlvflstlYNxg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-flow" "^7.2.0" -"@babel/plugin-transform-for-of@^7.2.0": +"@babel/plugin-transform-for-of@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz#0267fc735e24c808ba173866c6c4d1440fc3c556" integrity sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-function-name@^7.2.0": +"@babel/plugin-transform-function-name@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz#e1436116abb0610c2259094848754ac5230922ad" integrity sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA== @@ -446,7 +437,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-modules-amd@^7.2.0": +"@babel/plugin-transform-member-expression-literals@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz#fa10aa5c58a2cb6afcf2c9ffa8cb4d8b3d489a2d" + integrity sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-amd@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz#ef00435d46da0a5961aa728a1d2ecff063e4fb91" integrity sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg== @@ -455,26 +453,17 @@ "@babel/helper-plugin-utils" "^7.0.0" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-commonjs@^7.0.0 <7.4.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz#c4f1933f5991d5145e9cfad1dfd848ea1727f404" - integrity sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ== - dependencies: - "@babel/helper-module-transforms" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-simple-access" "^7.1.0" - -"@babel/plugin-transform-modules-commonjs@^7.2.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.5.0.tgz#425127e6045231360858eeaa47a71d75eded7a74" - integrity sha512-xmHq0B+ytyrWJvQTc5OWAC4ii6Dhr0s22STOoydokG51JjWhyYo5mRPXoi+ZmtHQhZZwuXNN+GG5jy5UZZJxIQ== +"@babel/plugin-transform-modules-commonjs@^7.4.4", "@babel/plugin-transform-modules-commonjs@^7.6.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz#39dfe957de4420445f1fcf88b68a2e4aa4515486" + integrity sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g== dependencies: "@babel/helper-module-transforms" "^7.4.4" "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-simple-access" "^7.1.0" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-systemjs@^7.3.4": +"@babel/plugin-transform-modules-systemjs@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz#e75266a13ef94202db2a0620977756f51d52d249" integrity sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg== @@ -491,21 +480,21 @@ "@babel/helper-module-transforms" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-named-capturing-groups-regex@^7.3.0": - version "7.4.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz#9d269fd28a370258199b4294736813a60bbdd106" - integrity sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg== +"@babel/plugin-transform-named-capturing-groups-regex@^7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.3.tgz#aaa6e409dd4fb2e50b6e2a91f7e3a3149dbce0cf" + integrity sha512-jTkk7/uE6H2s5w6VlMHeWuH+Pcy2lmdwFoeWCVnvIrDUnB5gQqTVI8WfmEAhF2CDEarGrknZcmSFg1+bkfCoSw== dependencies: - regexp-tree "^0.1.6" + regexpu-core "^4.6.0" -"@babel/plugin-transform-new-target@^7.0.0": +"@babel/plugin-transform-new-target@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz#18d120438b0cc9ee95a47f2c72bc9768fbed60a5" integrity sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-object-super@^7.2.0": +"@babel/plugin-transform-object-super@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz#c70021df834073c65eb613b8679cc4a381d1a9f9" integrity sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ== @@ -513,7 +502,7 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-replace-supers" "^7.5.5" -"@babel/plugin-transform-parameters@^7.2.0": +"@babel/plugin-transform-parameters@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz#7556cf03f318bd2719fe4c922d2d808be5571e16" integrity sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw== @@ -522,7 +511,14 @@ "@babel/helper-get-function-arity" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-react-jsx@^7.0.0 <7.4.0": +"@babel/plugin-transform-property-literals@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz#03e33f653f5b25c4eb572c98b9485055b389e905" + integrity sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-react-jsx@^7.0.0": version "7.3.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz#f2cab99026631c767e2745a5368b331cfe8f5290" integrity sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg== @@ -531,13 +527,20 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-jsx" "^7.2.0" -"@babel/plugin-transform-regenerator@^7.3.4": +"@babel/plugin-transform-regenerator@^7.4.5": version "7.4.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz#629dc82512c55cee01341fb27bdfcb210354680f" integrity sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA== dependencies: regenerator-transform "^0.14.0" +"@babel/plugin-transform-reserved-words@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz#4792af87c998a49367597d07fedf02636d2e1634" + integrity sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-shorthand-properties@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz#6333aee2f8d6ee7e28615457298934a3b46198f0" @@ -545,10 +548,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-spread@^7.2.0": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz#3103a9abe22f742b6d406ecd3cd49b774919b406" - integrity sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w== +"@babel/plugin-transform-spread@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz#fc77cf798b24b10c46e1b51b1b88c2bf661bb8dd" + integrity sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -560,7 +563,7 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.0.0" -"@babel/plugin-transform-template-literals@^7.2.0": +"@babel/plugin-transform-template-literals@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz#9d28fea7bbce637fb7612a0750989d8321d4bcb0" integrity sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g== @@ -575,137 +578,111 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-unicode-regex@^7.2.0": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz#ab4634bb4f14d36728bf5978322b35587787970f" - integrity sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA== +"@babel/plugin-transform-unicode-regex@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.6.2.tgz#b692aad888a7e8d8b1b214be6b9dc03d5031f698" + integrity sha512-orZI6cWlR3nk2YmYdb0gImrgCUwb5cBUwjf6Ks6dvNVvXERkwtJWOQaEOjPiu0Gu1Tq6Yq/hruCZZOOi9F34Dw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.5.4" + regexpu-core "^4.6.0" -"@babel/preset-env@^7.0.0 <7.4.0": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.4.tgz#887cf38b6d23c82f19b5135298bdb160062e33e1" - integrity sha512-2mwqfYMK8weA0g0uBKOt4FE3iEodiHy9/CW0b+nWXcbL+pGzLx8ESYc+j9IIxr6LTDHWKgPm71i9smo02bw+gA== +"@babel/preset-env@^7.4.4": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.3.tgz#9e1bf05a2e2d687036d24c40e4639dc46cef2271" + integrity sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-async-generator-functions" "^7.2.0" + "@babel/plugin-proposal-dynamic-import" "^7.5.0" "@babel/plugin-proposal-json-strings" "^7.2.0" - "@babel/plugin-proposal-object-rest-spread" "^7.3.4" + "@babel/plugin-proposal-object-rest-spread" "^7.6.2" "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.2.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.6.2" "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/plugin-syntax-dynamic-import" "^7.2.0" "@babel/plugin-syntax-json-strings" "^7.2.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" "@babel/plugin-transform-arrow-functions" "^7.2.0" - "@babel/plugin-transform-async-to-generator" "^7.3.4" + "@babel/plugin-transform-async-to-generator" "^7.5.0" "@babel/plugin-transform-block-scoped-functions" "^7.2.0" - "@babel/plugin-transform-block-scoping" "^7.3.4" - "@babel/plugin-transform-classes" "^7.3.4" + "@babel/plugin-transform-block-scoping" "^7.6.3" + "@babel/plugin-transform-classes" "^7.5.5" "@babel/plugin-transform-computed-properties" "^7.2.0" - "@babel/plugin-transform-destructuring" "^7.2.0" - "@babel/plugin-transform-dotall-regex" "^7.2.0" - "@babel/plugin-transform-duplicate-keys" "^7.2.0" + "@babel/plugin-transform-destructuring" "^7.6.0" + "@babel/plugin-transform-dotall-regex" "^7.6.2" + "@babel/plugin-transform-duplicate-keys" "^7.5.0" "@babel/plugin-transform-exponentiation-operator" "^7.2.0" - "@babel/plugin-transform-for-of" "^7.2.0" - "@babel/plugin-transform-function-name" "^7.2.0" + "@babel/plugin-transform-for-of" "^7.4.4" + "@babel/plugin-transform-function-name" "^7.4.4" "@babel/plugin-transform-literals" "^7.2.0" - "@babel/plugin-transform-modules-amd" "^7.2.0" - "@babel/plugin-transform-modules-commonjs" "^7.2.0" - "@babel/plugin-transform-modules-systemjs" "^7.3.4" + "@babel/plugin-transform-member-expression-literals" "^7.2.0" + "@babel/plugin-transform-modules-amd" "^7.5.0" + "@babel/plugin-transform-modules-commonjs" "^7.6.0" + "@babel/plugin-transform-modules-systemjs" "^7.5.0" "@babel/plugin-transform-modules-umd" "^7.2.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.3.0" - "@babel/plugin-transform-new-target" "^7.0.0" - "@babel/plugin-transform-object-super" "^7.2.0" - "@babel/plugin-transform-parameters" "^7.2.0" - "@babel/plugin-transform-regenerator" "^7.3.4" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.3" + "@babel/plugin-transform-new-target" "^7.4.4" + "@babel/plugin-transform-object-super" "^7.5.5" + "@babel/plugin-transform-parameters" "^7.4.4" + "@babel/plugin-transform-property-literals" "^7.2.0" + "@babel/plugin-transform-regenerator" "^7.4.5" + "@babel/plugin-transform-reserved-words" "^7.2.0" "@babel/plugin-transform-shorthand-properties" "^7.2.0" - "@babel/plugin-transform-spread" "^7.2.0" + "@babel/plugin-transform-spread" "^7.6.2" "@babel/plugin-transform-sticky-regex" "^7.2.0" - "@babel/plugin-transform-template-literals" "^7.2.0" + "@babel/plugin-transform-template-literals" "^7.4.4" "@babel/plugin-transform-typeof-symbol" "^7.2.0" - "@babel/plugin-transform-unicode-regex" "^7.2.0" - browserslist "^4.3.4" + "@babel/plugin-transform-unicode-regex" "^7.6.2" + "@babel/types" "^7.6.3" + browserslist "^4.6.0" + core-js-compat "^3.1.1" invariant "^2.2.2" js-levenshtein "^1.1.3" - semver "^5.3.0" - -"@babel/runtime@^7.0.0 <7.4.0": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83" - integrity sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g== - dependencies: - regenerator-runtime "^0.12.0" + semver "^5.5.0" -"@babel/template@^7.0.0 <7.4.0": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907" - integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g== +"@babel/runtime@^7.4.4": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.3.tgz#935122c74c73d2240cafd32ddb5fc2a6cd35cf1f" + integrity sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA== dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.2.2" - "@babel/types" "^7.2.2" + regenerator-runtime "^0.13.2" -"@babel/template@^7.1.0", "@babel/template@^7.2.2", "@babel/template@^7.4.0", "@babel/template@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" - integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw== +"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6" + integrity sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.4.4" - "@babel/types" "^7.4.4" + "@babel/parser" "^7.6.0" + "@babel/types" "^7.6.0" -"@babel/traverse@^7.0.0 <7.4.0": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.3.4.tgz#1330aab72234f8dea091b08c4f8b9d05c7119e06" - integrity sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.3.4" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.0.0" - "@babel/parser" "^7.3.4" - "@babel/types" "^7.3.4" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.11" - -"@babel/traverse@^7.1.0", "@babel/traverse@^7.3.4", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.5.5.tgz#f664f8f368ed32988cd648da9f72d5ca70f165bb" - integrity sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5", "@babel/traverse@^7.6.2", "@babel/traverse@^7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9" + integrity sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw== dependencies: "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.5.5" + "@babel/generator" "^7.6.3" "@babel/helper-function-name" "^7.1.0" "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.5.5" - "@babel/types" "^7.5.5" + "@babel/parser" "^7.6.3" + "@babel/types" "^7.6.3" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.4", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a" - integrity sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw== +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.6.0", "@babel/types@^7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09" + integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA== dependencies: esutils "^2.0.2" lodash "^4.17.13" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0 <7.4.0": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.4.tgz#bf482eaeaffb367a28abbf9357a94963235d90ed" - integrity sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ== - dependencies: - esutils "^2.0.2" - lodash "^4.17.11" - to-fast-properties "^2.0.0" - "@evocateur/libnpmaccess@^3.1.2": version "3.1.2" resolved "https://registry.yarnpkg.com/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845" @@ -746,16 +723,18 @@ safe-buffer "^5.1.2" "@evocateur/pacote@^9.6.3": - version "9.6.3" - resolved "https://registry.yarnpkg.com/@evocateur/pacote/-/pacote-9.6.3.tgz#bcd7adbd3c2ef303aa89bd24166f06dd9c080d89" - integrity sha512-ExqNqcbdHQprEgKnY/uQz7WRtyHRbQxRl4JnVkSkmtF8qffRrF9K+piZKNLNSkRMOT/3H0e3IP44QVCHaXMWOQ== + version "9.6.5" + resolved "https://registry.yarnpkg.com/@evocateur/pacote/-/pacote-9.6.5.tgz#33de32ba210b6f17c20ebab4d497efc6755f4ae5" + integrity sha512-EI552lf0aG2nOV8NnZpTxNo2PcXKPmDbF9K8eCBFQdIZwHNGN/mi815fxtmUMa2wTa1yndotICIDt/V0vpEx2w== dependencies: "@evocateur/npm-registry-fetch" "^4.0.0" bluebird "^3.5.3" - cacache "^12.0.0" + cacache "^12.0.3" + chownr "^1.1.2" figgy-pudding "^3.5.1" get-stream "^4.1.0" glob "^7.1.4" + infer-owner "^1.0.4" lru-cache "^5.1.1" make-fetch-happen "^5.0.0" minimatch "^3.0.4" @@ -765,7 +744,7 @@ normalize-package-data "^2.5.0" npm-package-arg "^6.1.0" npm-packlist "^1.4.4" - npm-pick-manifest "^2.2.3" + npm-pick-manifest "^3.0.0" osenv "^0.1.5" promise-inflight "^1.0.1" promise-retry "^1.1.1" @@ -783,15 +762,15 @@ resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.3.tgz#f060bf6eaafae4d56a7dac618980838b0696e2ab" integrity sha512-FmuxfCuolpLl0AnQ2NHSzoUKWEJDFl63qXjzdoWBVyFCXzMGm1spBzk7LeHNoVCiWCF7mRVms9e6jEV9+MoPbg== -"@lerna/add@3.16.2": - version "3.16.2" - resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.16.2.tgz#90ecc1be7051cfcec75496ce122f656295bd6e94" - integrity sha512-RAAaF8aODPogj2Ge9Wj3uxPFIBGpog9M+HwSuq03ZnkkO831AmasCTJDqV+GEpl1U2DvnhZQEwHpWmTT0uUeEw== +"@lerna/add@3.17.0": + version "3.17.0" + resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.17.0.tgz#d5cdba14a489890134e5043d59ec64a07557ed97" + integrity sha512-On98kKbYRfUGYzCb2MUWCZyUmwNl1Acbd66CM+hDSrYEPZ7W3uq56Tfya6De+cHGOPWWNWoss3C0ZyKILXpjAA== dependencies: "@evocateur/pacote" "^9.6.3" - "@lerna/bootstrap" "3.16.2" - "@lerna/command" "3.16.0" - "@lerna/filter-options" "3.16.0" + "@lerna/bootstrap" "3.17.0" + "@lerna/command" "3.16.5" + "@lerna/filter-options" "3.16.5" "@lerna/npm-conf" "3.16.0" "@lerna/validation-error" "3.13.0" dedent "^0.7.0" @@ -799,31 +778,22 @@ p-map "^2.1.0" semver "^6.2.0" -"@lerna/batch-packages@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/batch-packages/-/batch-packages-3.16.0.tgz#1c16cb697e7d718177db744cbcbdac4e30253c8c" - integrity sha512-7AdMkANpubY/FKFI01im01tlx6ygOBJ/0JcixMUWoWP/7Ds3SWQF22ID6fbBr38jUWptYLDs2fagtTDL7YUPuA== +"@lerna/bootstrap@3.17.0": + version "3.17.0" + resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-3.17.0.tgz#5572c75bd0b5402caca53155761454589fe901b4" + integrity sha512-2wQEwEtzU1UP7ZuzHtjJPgFxWXY5OectT+sjIFrJDE7C9n9nbAkL9MI/IM2X3RbgRK+sIxbhEqyb9o+0Yfzk1A== dependencies: - "@lerna/package-graph" "3.16.0" - npmlog "^4.1.2" - -"@lerna/bootstrap@3.16.2": - version "3.16.2" - resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-3.16.2.tgz#be268d940221d3c3270656b9b791b492559ad9d8" - integrity sha512-I+gs7eh6rv9Vyd+CwqL7sftRfOOsSzCle8cv/CGlMN7/p7EAVhxEdAw8SYoHIKHzipXszuqqy1Y3opyleD0qdA== - dependencies: - "@lerna/batch-packages" "3.16.0" - "@lerna/command" "3.16.0" - "@lerna/filter-options" "3.16.0" - "@lerna/has-npm-version" "3.16.0" - "@lerna/npm-install" "3.16.0" + "@lerna/command" "3.16.5" + "@lerna/filter-options" "3.16.5" + "@lerna/has-npm-version" "3.16.5" + "@lerna/npm-install" "3.16.5" "@lerna/package-graph" "3.16.0" "@lerna/pulse-till-done" "3.13.0" - "@lerna/rimraf-dir" "3.14.2" + "@lerna/rimraf-dir" "3.16.5" "@lerna/run-lifecycle" "3.16.2" - "@lerna/run-parallel-batches" "3.16.0" - "@lerna/symlink-binary" "3.16.2" - "@lerna/symlink-dependencies" "3.16.2" + "@lerna/run-topologically" "3.16.0" + "@lerna/symlink-binary" "3.17.0" + "@lerna/symlink-dependencies" "3.17.0" "@lerna/validation-error" "3.13.0" dedent "^0.7.0" get-port "^4.2.0" @@ -837,45 +807,45 @@ read-package-tree "^5.1.6" semver "^6.2.0" -"@lerna/changed@3.16.4": - version "3.16.4" - resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-3.16.4.tgz#c3e727d01453513140eee32c94b695de577dc955" - integrity sha512-NCD7XkK744T23iW0wqKEgF4R9MYmReUbyHCZKopFnsNpQdqumc3SOIvQUAkKCP6hQJmYvxvOieoVgy/CVDpZ5g== +"@lerna/changed@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-3.16.5.tgz#62426ffc9427daa201e8fd4a13685a0dbfe3f3c7" + integrity sha512-Sj66BK/QyYv7YxAQrFg6H+7X68OnSKsVVyTMKtfIFkj1t8ey67DNav0Y14AGNQq+CX0CtaiA0ZybC0KJcjtMDQ== dependencies: - "@lerna/collect-updates" "3.16.0" - "@lerna/command" "3.16.0" + "@lerna/collect-updates" "3.16.5" + "@lerna/command" "3.16.5" "@lerna/listable" "3.16.0" "@lerna/output" "3.13.0" - "@lerna/version" "3.16.4" + "@lerna/version" "3.16.5" -"@lerna/check-working-tree@3.14.2": - version "3.14.2" - resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-3.14.2.tgz#5ce007722180a69643a8456766ed8a91fc7e9ae1" - integrity sha512-7safqxM/MYoAoxZxulUDtIJIbnBIgo0PB/FHytueG+9VaX7GMnDte2Bt1EKa0dz2sAyQdmQ3Q8ZXpf/6JDjaeg== +"@lerna/check-working-tree@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-3.16.5.tgz#b4f8ae61bb4523561dfb9f8f8d874dd46bb44baa" + integrity sha512-xWjVBcuhvB8+UmCSb5tKVLB5OuzSpw96WEhS2uz6hkWVa/Euh1A0/HJwn2cemyK47wUrCQXtczBUiqnq9yX5VQ== dependencies: - "@lerna/collect-uncommitted" "3.14.2" - "@lerna/describe-ref" "3.14.2" + "@lerna/collect-uncommitted" "3.16.5" + "@lerna/describe-ref" "3.16.5" "@lerna/validation-error" "3.13.0" -"@lerna/child-process@3.14.2": - version "3.14.2" - resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-3.14.2.tgz#950240cba83f7dfe25247cfa6c9cebf30b7d94f6" - integrity sha512-xnq+W5yQb6RkwI0p16ZQnrn6HkloH/MWTw4lGE1nKsBLAUbmSU5oTE93W1nrG0X3IMF/xWc9UYvNdUGMWvZZ4w== +"@lerna/child-process@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-3.16.5.tgz#38fa3c18064aa4ac0754ad80114776a7b36a69b2" + integrity sha512-vdcI7mzei9ERRV4oO8Y1LHBZ3A5+ampRKg1wq5nutLsUA4mEBN6H7JqjWOMY9xZemv6+kATm2ofjJ3lW5TszQg== dependencies: chalk "^2.3.1" execa "^1.0.0" strong-log-transformer "^2.0.0" -"@lerna/clean@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-3.16.0.tgz#1c134334cacea1b1dbeacdc580e8b9240db8efa1" - integrity sha512-5P9U5Y19WmYZr7UAMGXBpY7xCRdlR7zhHy8MAPDKVx70rFIBS6nWXn5n7Kntv74g7Lm1gJ2rsiH5tj1OPcRJgg== +"@lerna/clean@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-3.16.5.tgz#1177bb404245c8a9db2b722ec53849d619fec0de" + integrity sha512-PT//BXS11bf+lHF3LYVw+24/Rxk+vXBqZIsx8p1+ICia/lYXlxUgF90IQFGAT0OTu82j014VgozggoI+C3eLWw== dependencies: - "@lerna/command" "3.16.0" - "@lerna/filter-options" "3.16.0" + "@lerna/command" "3.16.5" + "@lerna/filter-options" "3.16.5" "@lerna/prompt" "3.13.0" "@lerna/pulse-till-done" "3.13.0" - "@lerna/rimraf-dir" "3.14.2" + "@lerna/rimraf-dir" "3.16.5" p-map "^2.1.0" p-map-series "^1.0.0" p-waterfall "^1.0.0" @@ -890,33 +860,33 @@ npmlog "^4.1.2" yargs "^12.0.1" -"@lerna/collect-uncommitted@3.14.2": - version "3.14.2" - resolved "https://registry.yarnpkg.com/@lerna/collect-uncommitted/-/collect-uncommitted-3.14.2.tgz#b5ed00d800bea26bb0d18404432b051eee8d030e" - integrity sha512-4EkQu4jIOdNL2BMzy/N0ydHB8+Z6syu6xiiKXOoFl0WoWU9H1jEJCX4TH7CmVxXL1+jcs8FIS2pfQz4oew99Eg== +"@lerna/collect-uncommitted@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/collect-uncommitted/-/collect-uncommitted-3.16.5.tgz#a494d61aac31cdc7aec4bbe52c96550274132e63" + integrity sha512-ZgqnGwpDZiWyzIQVZtQaj9tRizsL4dUOhuOStWgTAw1EMe47cvAY2kL709DzxFhjr6JpJSjXV5rZEAeU3VE0Hg== dependencies: - "@lerna/child-process" "3.14.2" + "@lerna/child-process" "3.16.5" chalk "^2.3.1" figgy-pudding "^3.5.1" npmlog "^4.1.2" -"@lerna/collect-updates@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-3.16.0.tgz#6db3ce8a740a4e2b972c033a63bdfb77f2553d8c" - integrity sha512-HwAIl815X2TNlmcp28zCrSdXfoZWNP7GJPEqNWYk7xDJTYLqQ+SrmKUePjb3AMGBwYAraZSEJLbHdBpJ5+cHmQ== +"@lerna/collect-updates@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-3.16.5.tgz#26496d6036ae4b421d211fc332c37a996b181dc0" + integrity sha512-JWeN/ghfQ0llfPtUWtNSHRCqAncHGF0hznsTVqxCoQ3j8GacgYaBLfC3FsUfTnUm8BQ1pi7prAclMoBvfmMwyQ== dependencies: - "@lerna/child-process" "3.14.2" - "@lerna/describe-ref" "3.14.2" + "@lerna/child-process" "3.16.5" + "@lerna/describe-ref" "3.16.5" minimatch "^3.0.4" npmlog "^4.1.2" slash "^2.0.0" -"@lerna/command@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/command/-/command-3.16.0.tgz#ba3dba49cb5ce4d11b48269cf95becd86e30773f" - integrity sha512-u7tE4GC4/gfbPA9eQg+0ulnoJ+PMoMqomx033r/IxqZrHtmJR9+pF/37S0fsxJ2hX/RMFPC7c9Q/i8NEufSpdQ== +"@lerna/command@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/command/-/command-3.16.5.tgz#3a889acbd0d39362b37445ba4ced01878bcb4fba" + integrity sha512-sXv+a+ljEfW6aEKxmnv3rLbbWpDQi3IVdDoezCATkbqMYUssZGz43UwUVuaYikViB86SLBbtprFrVcZBaqAfCQ== dependencies: - "@lerna/child-process" "3.14.2" + "@lerna/child-process" "3.16.5" "@lerna/package-graph" "3.16.0" "@lerna/project" "3.16.0" "@lerna/validation-error" "3.13.0" @@ -953,14 +923,14 @@ fs-extra "^8.1.0" npmlog "^4.1.2" -"@lerna/create@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/create/-/create-3.16.0.tgz#4de841ec7d98b29bb19fb7d6ad982e65f7a150e8" - integrity sha512-OZApR1Iz7awutbmj4sAArwhqCyKgcrnw9rH0aWAUrkYWrD1w4TwkvAcYAsfx5GpQGbLQwoXhoyyPwPfZRRWz3Q== +"@lerna/create@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/create/-/create-3.16.5.tgz#e733c767ba11f6ef89cecafb1f0ed094c90bcdae" + integrity sha512-eScA3iNhjeVAaaNDaVVmsupM4Sulmr4AQVPEfNUN+f6aU7KuvBwbe0Nh46xtQhgNTcSSWj9pmO2IisTrzq4ezA== dependencies: "@evocateur/pacote" "^9.6.3" - "@lerna/child-process" "3.14.2" - "@lerna/command" "3.16.0" + "@lerna/child-process" "3.16.5" + "@lerna/command" "3.16.5" "@lerna/npm-conf" "3.16.0" "@lerna/validation-error" "3.13.0" camelcase "^5.0.0" @@ -977,42 +947,42 @@ validate-npm-package-name "^3.0.0" whatwg-url "^7.0.0" -"@lerna/describe-ref@3.14.2": - version "3.14.2" - resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-3.14.2.tgz#edc3c973f5ca9728d23358c4f4d3b55a21f65be5" - integrity sha512-qa5pzDRK2oBQXNjyRmRnN7E8a78NMYfQjjlRFB0KNHMsT6mCiL9+8kIS39sSE2NqT8p7xVNo2r2KAS8R/m3CoQ== +"@lerna/describe-ref@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-3.16.5.tgz#a338c25aaed837d3dc70b8a72c447c5c66346ac0" + integrity sha512-c01+4gUF0saOOtDBzbLMFOTJDHTKbDFNErEY6q6i9QaXuzy9LNN62z+Hw4acAAZuJQhrVWncVathcmkkjvSVGw== dependencies: - "@lerna/child-process" "3.14.2" + "@lerna/child-process" "3.16.5" npmlog "^4.1.2" -"@lerna/diff@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-3.16.0.tgz#6d09a786f9f5b343a2fdc460eb0be08a05b420aa" - integrity sha512-QUpVs5TPl8vBIne10/vyjUxanQBQQp7Lk3iaB8MnCysKr0O+oy7trWeFVDPEkBTCD177By7yPGyW5Yey1nCBbA== +"@lerna/diff@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-3.16.5.tgz#5b5ceb596f562e7329cbb50d27ed5ec4231e754f" + integrity sha512-19Nchn4Yem/FyNqXSMzv3RP3/jRBTpu1i/Z/nCrt5lA0D2fFv9uCh9aE2XnzqZ0r7qiGJZNOMax/TIOqq3KtFA== dependencies: - "@lerna/child-process" "3.14.2" - "@lerna/command" "3.16.0" + "@lerna/child-process" "3.16.5" + "@lerna/command" "3.16.5" "@lerna/validation-error" "3.13.0" npmlog "^4.1.2" -"@lerna/exec@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-3.16.0.tgz#2b6c033cee46181b6eede0eb12aad5c2c0181e89" - integrity sha512-mH3O5NXf/O88jBaBBTUf+d56CUkxpg782s3Jxy7HWbVuSUULt3iMRPTh+zEXO5/555etsIVVDDyUR76meklrJA== +"@lerna/exec@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-3.16.5.tgz#2a3055263d89dc59d593f0eaab4f22286d47e142" + integrity sha512-z7ceaYr3B9Zzmf5TlPulMNOKhsq6emzWSuiTX57eMWCnVfqDt34dM89HredJwFAmxLSlhqHuGQOhwyOaEY7+2g== dependencies: - "@lerna/child-process" "3.14.2" - "@lerna/command" "3.16.0" - "@lerna/filter-options" "3.16.0" + "@lerna/child-process" "3.16.5" + "@lerna/command" "3.16.5" + "@lerna/filter-options" "3.16.5" "@lerna/run-topologically" "3.16.0" "@lerna/validation-error" "3.13.0" p-map "^2.1.0" -"@lerna/filter-options@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-3.16.0.tgz#b1660b4480c02a5c6efa4d0cd98b9afde4ed0bba" - integrity sha512-InIi1fF8+PxpCwir9bIy+pGxrdE6hvN0enIs1eNGCVS1TTE8osNgiZXa838bMQ1yaEccdcnVX6Z03BNKd56kNg== +"@lerna/filter-options@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-3.16.5.tgz#2137a75c0e5aa28c9bdfb75f53755aa28abfa202" + integrity sha512-PnkrDPJHvQ3k19JFG8jJVasVbZhg+Ckg5u9aVA254T3BSA2CT7MtXjB+snS76npe83170zII0iYufDUY4rhm0A== dependencies: - "@lerna/collect-updates" "3.16.0" + "@lerna/collect-updates" "3.16.5" "@lerna/filter-packages" "3.16.0" dedent "^0.7.0" @@ -1041,12 +1011,12 @@ ssri "^6.0.1" tar "^4.4.8" -"@lerna/github-client@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-3.16.0.tgz#619874e461641d4f59ab1b3f1a7ba22dba88125d" - integrity sha512-IVJjcKjkYaUEPJsDyAblHGEFFNKCRyMagbIDm14L7Ab94ccN6i4TKOqAFEJn2SJHYvKKBdp3Zj2zNlASOMe3DA== +"@lerna/github-client@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-3.16.5.tgz#2eb0235c3bf7a7e5d92d73e09b3761ab21f35c2e" + integrity sha512-rHQdn8Dv/CJrO3VouOP66zAcJzrHsm+wFuZ4uGAai2At2NkgKH+tpNhQy2H1PSC0Ezj9LxvdaHYrUzULqVK5Hw== dependencies: - "@lerna/child-process" "3.14.2" + "@lerna/child-process" "3.16.5" "@octokit/plugin-enterprise-rest" "^3.6.1" "@octokit/rest" "^16.28.4" git-url-parse "^11.1.2" @@ -1066,21 +1036,21 @@ resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-3.13.0.tgz#217662290db06ad9cf2c49d8e3100ee28eaebae1" integrity sha512-SlZvh1gVRRzYLVluz9fryY1nJpZ0FHDGB66U9tFfvnnxmueckRQxLopn3tXj3NU1kc3QANT2I5BsQkOqZ4TEFQ== -"@lerna/has-npm-version@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-3.16.0.tgz#55764a4ce792f0c8553cf996a17f554b9e843288" - integrity sha512-TIY036dA9J8OyTrZq9J+it2DVKifL65k7hK8HhkUPpitJkw6jwbMObA/8D40LOGgWNPweJWqmlrTbRSwsR7DrQ== +"@lerna/has-npm-version@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-3.16.5.tgz#ab83956f211d8923ea6afe9b979b38cc73b15326" + integrity sha512-WL7LycR9bkftyqbYop5rEGJ9sRFIV55tSGmbN1HLrF9idwOCD7CLrT64t235t3t4O5gehDnwKI5h2U3oxTrF8Q== dependencies: - "@lerna/child-process" "3.14.2" + "@lerna/child-process" "3.16.5" semver "^6.2.0" -"@lerna/import@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/import/-/import-3.16.0.tgz#b57cb453f4acfc60f6541fcbba10674055cb179d" - integrity sha512-trsOmGHzw0rL/f8BLNvd+9PjoTkXq2Dt4/V2UCha254hMQaYutbxcYu8iKPxz9x86jSPlH7FpbTkkHXDsoY7Yg== +"@lerna/import@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/import/-/import-3.16.5.tgz#3fe1f11ad25ff929963b8d7e331ac391c4920947" + integrity sha512-n5zy9zeNziS/jex/rHiw7YSpnsfGYXBLv4RSm0gnKouV+dvKocUd139S0oHJG3oQgL+B6anZpR/3ajxz4QcZ4w== dependencies: - "@lerna/child-process" "3.14.2" - "@lerna/command" "3.16.0" + "@lerna/child-process" "3.16.5" + "@lerna/command" "3.16.5" "@lerna/prompt" "3.13.0" "@lerna/pulse-till-done" "3.13.0" "@lerna/validation-error" "3.13.0" @@ -1088,35 +1058,35 @@ fs-extra "^8.1.0" p-map-series "^1.0.0" -"@lerna/init@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/init/-/init-3.16.0.tgz#31e0d66bbededee603338b487a42674a072b7a7d" - integrity sha512-Ybol/x5xMtBgokx4j7/Y3u0ZmNh0NiSWzBFVaOs2NOJKvuqrWimF67DKVz7yYtTYEjtaMdug64ohFF4jcT/iag== +"@lerna/init@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/init/-/init-3.16.5.tgz#ca45889d6d15c46e26d1b45b59ef455006df7f68" + integrity sha512-K8JtSHbPxR5pZHJ0GUXGhMdx+E/pDnbp8JbTUkEkLCyRHp3C0VFAtINJ+ysSpObleTFivA1xrgwqG8JbgI213Q== dependencies: - "@lerna/child-process" "3.14.2" - "@lerna/command" "3.16.0" + "@lerna/child-process" "3.16.5" + "@lerna/command" "3.16.5" fs-extra "^8.1.0" p-map "^2.1.0" write-json-file "^3.2.0" -"@lerna/link@3.16.2": - version "3.16.2" - resolved "https://registry.yarnpkg.com/@lerna/link/-/link-3.16.2.tgz#6c3a5658f6448a64dddca93d9348ac756776f6f6" - integrity sha512-eCPg5Lo8HT525fIivNoYF3vWghO3UgEVFdbsiPmhzwI7IQyZro5HWYzLtywSAdEog5XZpd2Bbn0CsoHWBB3gww== +"@lerna/link@3.17.0": + version "3.17.0" + resolved "https://registry.yarnpkg.com/@lerna/link/-/link-3.17.0.tgz#4a08eb384342d4e530b16d47ebfade9a2c1dbeeb" + integrity sha512-eh8+mAH74ndxigBQIW5jXO1md8jpTzpzHHiRwCoetRr3QM2oLgYbWczZDViCdWV4R4J6BLbyp3Jh64cob+leyA== dependencies: - "@lerna/command" "3.16.0" + "@lerna/command" "3.16.5" "@lerna/package-graph" "3.16.0" - "@lerna/symlink-dependencies" "3.16.2" + "@lerna/symlink-dependencies" "3.17.0" p-map "^2.1.0" slash "^2.0.0" -"@lerna/list@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/list/-/list-3.16.0.tgz#883c00b2baf1e03c93e54391372f67a01b773c2f" - integrity sha512-TkvstoPsgKqqQ0KfRumpsdMXfRSEhdXqOLq519XyI5IRWYxhoqXqfi8gG37UoBPhBNoe64japn5OjphF3rOmQA== +"@lerna/list@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/list/-/list-3.16.5.tgz#eb27d826a1614d447377b446a552570bc1744830" + integrity sha512-HJgJigTyIvLOWvdW5++Ewam+owk2aNPg/niqqIaV90OtzsEd55Cqb2ziIWdFLRFLYPu66HHhJOXBnGfP1uNl9A== dependencies: - "@lerna/command" "3.16.0" - "@lerna/filter-options" "3.16.0" + "@lerna/command" "3.16.5" + "@lerna/filter-options" "3.16.5" "@lerna/listable" "3.16.0" "@lerna/output" "3.13.0" @@ -1158,12 +1128,12 @@ npm-package-arg "^6.1.0" npmlog "^4.1.2" -"@lerna/npm-install@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-3.16.0.tgz#8ec76a7a13b183bde438fd46296bf7a0d6f86017" - integrity sha512-APUOIilZCzDzce92uLEwzt1r7AEMKT/hWA1ThGJL+PO9Rn8A95Km3o2XZAYG4W0hR+P4O2nSVuKbsjQtz8CjFQ== +"@lerna/npm-install@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-3.16.5.tgz#d6bfdc16f81285da66515ae47924d6e278d637d3" + integrity sha512-hfiKk8Eku6rB9uApqsalHHTHY+mOrrHeWEs+gtg7+meQZMTS3kzv4oVp5cBZigndQr3knTLjwthT/FX4KvseFg== dependencies: - "@lerna/child-process" "3.14.2" + "@lerna/child-process" "3.16.5" "@lerna/get-npm-exec-opts" "3.13.0" fs-extra "^8.1.0" npm-package-arg "^6.1.0" @@ -1186,12 +1156,12 @@ pify "^4.0.1" read-package-json "^2.0.13" -"@lerna/npm-run-script@3.14.2": - version "3.14.2" - resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-3.14.2.tgz#8c518ea9d241a641273e77aad6f6fddc16779c3f" - integrity sha512-LbVFv+nvAoRTYLMrJlJ8RiakHXrLslL7Jp/m1R18vYrB8LYWA3ey+nz5Tel2OELzmjUiemAKZsD9h6i+Re5egg== +"@lerna/npm-run-script@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-3.16.5.tgz#9c2ec82453a26c0b46edc0bb7c15816c821f5c15" + integrity sha512-1asRi+LjmVn3pMjEdpqKJZFT/3ZNpb+VVeJMwrJaV/3DivdNg7XlPK9LTrORuKU4PSvhdEZvJmSlxCKyDpiXsQ== dependencies: - "@lerna/child-process" "3.14.2" + "@lerna/child-process" "3.16.5" "@lerna/get-npm-exec-opts" "3.13.0" npmlog "^4.1.2" @@ -1277,19 +1247,19 @@ inquirer "^6.2.0" npmlog "^4.1.2" -"@lerna/publish@3.16.4": - version "3.16.4" - resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-3.16.4.tgz#4cd55d8be9943d9a68e316e930a90cda8590500e" - integrity sha512-XZY+gRuF7/v6PDQwl7lvZaGWs8CnX6WIPIu+OCcyFPSL/rdWegdN7HieKBHskgX798qRQc2GrveaY7bNoTKXAw== +"@lerna/publish@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-3.16.5.tgz#d101f3a18ea117269c997ef5ba65117d65cafd08" + integrity sha512-gJzvzeOWj0d4+RWCAcCyvFXN246Dwl2WpOLtWKwdFUC3fz+tvCI7FO8moPbaJt6EqYRMDaoeqVQnIrSeisbZDw== dependencies: "@evocateur/libnpmaccess" "^3.1.2" "@evocateur/npm-registry-fetch" "^4.0.0" "@evocateur/pacote" "^9.6.3" - "@lerna/check-working-tree" "3.14.2" - "@lerna/child-process" "3.14.2" - "@lerna/collect-updates" "3.16.0" - "@lerna/command" "3.16.0" - "@lerna/describe-ref" "3.14.2" + "@lerna/check-working-tree" "3.16.5" + "@lerna/child-process" "3.16.5" + "@lerna/collect-updates" "3.16.5" + "@lerna/command" "3.16.5" + "@lerna/describe-ref" "3.16.5" "@lerna/log-packed" "3.16.0" "@lerna/npm-conf" "3.16.0" "@lerna/npm-dist-tag" "3.16.0" @@ -1303,7 +1273,7 @@ "@lerna/run-lifecycle" "3.16.2" "@lerna/run-topologically" "3.16.0" "@lerna/validation-error" "3.13.0" - "@lerna/version" "3.16.4" + "@lerna/version" "3.16.5" figgy-pudding "^3.5.1" fs-extra "^8.1.0" npm-package-arg "^6.1.0" @@ -1337,12 +1307,12 @@ npmlog "^4.1.2" read-cmd-shim "^1.0.1" -"@lerna/rimraf-dir@3.14.2": - version "3.14.2" - resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-3.14.2.tgz#103a49882abd85d42285d05cc76869b89f21ffd2" - integrity sha512-eFNkZsy44Bu9v1Hrj5Zk6omzg8O9h/7W6QYK1TTUHeyrjTEwytaNQlqF0lrTLmEvq55sviV42NC/8P3M2cvq8Q== +"@lerna/rimraf-dir@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-3.16.5.tgz#04316ab5ffd2909657aaf388ea502cb8c2f20a09" + integrity sha512-bQlKmO0pXUsXoF8lOLknhyQjOZsCc0bosQDoX4lujBXSWxHVTg1VxURtWf2lUjz/ACsJVDfvHZbDm8kyBk5okA== dependencies: - "@lerna/child-process" "3.14.2" + "@lerna/child-process" "3.16.5" npmlog "^4.1.2" path-exists "^3.0.0" rimraf "^2.6.2" @@ -1357,14 +1327,6 @@ npm-lifecycle "^3.1.2" npmlog "^4.1.2" -"@lerna/run-parallel-batches@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/run-parallel-batches/-/run-parallel-batches-3.16.0.tgz#5ace7911a2dd31dfd1e53c61356034e27df0e1fb" - integrity sha512-2J/Nyv+MvogmQEfC7VcS21ifk7w0HVvzo2yOZRPvkCzGRu/rducxtB4RTcr58XCZ8h/Bt1aqQYKExu3c/3GXwg== - dependencies: - p-map "^2.1.0" - p-map-series "^1.0.0" - "@lerna/run-topologically@3.16.0": version "3.16.0" resolved "https://registry.yarnpkg.com/@lerna/run-topologically/-/run-topologically-3.16.0.tgz#39e29cfc628bbc8e736d8e0d0e984997ac01bbf5" @@ -1374,38 +1336,38 @@ figgy-pudding "^3.5.1" p-queue "^4.0.0" -"@lerna/run@3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@lerna/run/-/run-3.16.0.tgz#1ea568c6f303e47fa00b3403a457836d40738fd2" - integrity sha512-woTeLlB1OAAz4zzjdI6RyIxSGuxiUPHJZm89E1pDEPoWwtQV6HMdMgrsQd9ATsJ5Ez280HH4bF/LStAlqW8Ufg== +"@lerna/run@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/run/-/run-3.16.5.tgz#66466fbe3edfe94b29f53341c05e7a509b1e8388" + integrity sha512-sORjqiGJvbLhax/QE9IfTKsvUGfNDu8bUkxxhnbxYu0tGhgWhiPzRVZ564QG9zQ6D23CGd/wQ0AHyrsRPulbzQ== dependencies: - "@lerna/command" "3.16.0" - "@lerna/filter-options" "3.16.0" - "@lerna/npm-run-script" "3.14.2" + "@lerna/command" "3.16.5" + "@lerna/filter-options" "3.16.5" + "@lerna/npm-run-script" "3.16.5" "@lerna/output" "3.13.0" "@lerna/run-topologically" "3.16.0" "@lerna/timer" "3.13.0" "@lerna/validation-error" "3.13.0" p-map "^2.1.0" -"@lerna/symlink-binary@3.16.2": - version "3.16.2" - resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-3.16.2.tgz#f98a3d9da9e56f1d302dc0d5c2efeb951483ee66" - integrity sha512-kz9XVoFOGSF83gg4gBqH+mG6uxfJfTp8Uy+Cam40CvMiuzfODrGkjuBEFoM/uO2QOAwZvbQDYOBpKUa9ZxHS1Q== +"@lerna/symlink-binary@3.17.0": + version "3.17.0" + resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-3.17.0.tgz#8f8031b309863814883d3f009877f82e38aef45a" + integrity sha512-RLpy9UY6+3nT5J+5jkM5MZyMmjNHxZIZvXLV+Q3MXrf7Eaa1hNqyynyj4RO95fxbS+EZc4XVSk25DGFQbcRNSQ== dependencies: "@lerna/create-symlink" "3.16.2" "@lerna/package" "3.16.0" fs-extra "^8.1.0" p-map "^2.1.0" -"@lerna/symlink-dependencies@3.16.2": - version "3.16.2" - resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-3.16.2.tgz#91d9909d35897aebd76a03644a00cd03c4128240" - integrity sha512-wnZqGJQ+Jvr1I3inxrkffrFZfmQI7Ta8gySw/UWCy95QtZWF/f5yk8zVIocCAsjzD0wgb3jJE3CFJ9W5iwWk1A== +"@lerna/symlink-dependencies@3.17.0": + version "3.17.0" + resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-3.17.0.tgz#48d6360e985865a0e56cd8b51b308a526308784a" + integrity sha512-KmjU5YT1bpt6coOmdFueTJ7DFJL4H1w5eF8yAQ2zsGNTtZ+i5SGFBWpb9AQaw168dydc3s4eu0W0Sirda+F59Q== dependencies: "@lerna/create-symlink" "3.16.2" "@lerna/resolve-symlink" "3.16.0" - "@lerna/symlink-binary" "3.16.2" + "@lerna/symlink-binary" "3.17.0" fs-extra "^8.1.0" p-finally "^1.0.0" p-map "^2.1.0" @@ -1423,17 +1385,17 @@ dependencies: npmlog "^4.1.2" -"@lerna/version@3.16.4": - version "3.16.4" - resolved "https://registry.yarnpkg.com/@lerna/version/-/version-3.16.4.tgz#b5cc37f3ad98358d599c6196c30b6efc396d42bf" - integrity sha512-ikhbMeIn5ljCtWTlHDzO4YvTmpGTX1lWFFIZ79Vd1TNyOr+OUuKLo/+p06mCl2WEdZu0W2s5E9oxfAAQbyDxEg== +"@lerna/version@3.16.5": + version "3.16.5" + resolved "https://registry.yarnpkg.com/@lerna/version/-/version-3.16.5.tgz#5e4726e623d67f2ae3af9833a14fb71b2d04f09e" + integrity sha512-GHwIqC6rLldpn7e4P/Ms+ygu9nC/1UJidpaBa6qhvuXlIaqJuFKdQGHTXfuvCBUS+/LTA3Cb9cQZgob9aocOkA== dependencies: - "@lerna/check-working-tree" "3.14.2" - "@lerna/child-process" "3.14.2" - "@lerna/collect-updates" "3.16.0" - "@lerna/command" "3.16.0" + "@lerna/check-working-tree" "3.16.5" + "@lerna/child-process" "3.16.5" + "@lerna/collect-updates" "3.16.5" + "@lerna/command" "3.16.5" "@lerna/conventional-commits" "3.16.4" - "@lerna/github-client" "3.16.0" + "@lerna/github-client" "3.16.5" "@lerna/gitlab-client" "3.15.0" "@lerna/output" "3.13.0" "@lerna/prerelease-id-from-version" "3.16.0" @@ -1475,14 +1437,12 @@ integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== "@octokit/endpoint@^5.1.0": - version "5.3.2" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-5.3.2.tgz#2deda2d869cac9ba7f370287d55667be2a808d4b" - integrity sha512-gRjteEM9I6f4D8vtwU2iGUTn9RX/AJ0SVXiqBUEuYEWVGGAVjSXdT0oNmghH5lvQNWs8mwt6ZaultuG6yXivNw== + version "5.4.1" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-5.4.1.tgz#8f4c747d6cf8f352683d35a7fe8664db487cb730" + integrity sha512-iwn46orWg3F4iqIzAVRfbzhnROyx7BQ7zJE0B7SEeaMIBvk3qmWtswtRk14QkMNUuNiCHQ6mAM00VJxWqrdM1g== dependencies: - deepmerge "4.0.0" is-plain-object "^3.0.0" - universal-user-agent "^3.0.0" - url-template "^2.0.8" + universal-user-agent "^4.0.0" "@octokit/plugin-enterprise-rest@^3.6.1": version "3.6.2" @@ -1497,10 +1457,10 @@ deprecation "^2.0.0" once "^1.4.0" -"@octokit/request@^5.0.0": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.0.2.tgz#59a920451f24811c016ddc507adcc41aafb2dca5" - integrity sha512-z1BQr43g4kOL4ZrIVBMHwi68Yg9VbkRUyuAgqCp1rU3vbYa69+2gIld/+gHclw15bJWQnhqqyEb7h5a5EqgZ0A== +"@octokit/request@^5.2.0": + version "5.2.1" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.2.1.tgz#d8076b4bd415802c2dbffc82cf9b8b78f49551a3" + integrity sha512-onjQo4QKyiMAqLM6j3eH8vWw1LEfNCpoZUl6a+TrZVJM1wysBC8F0GhK9K/Vc9UsScSmVs2bstOVD34xpQ2wqQ== dependencies: "@octokit/endpoint" "^5.1.0" "@octokit/request-error" "^1.0.1" @@ -1508,14 +1468,14 @@ is-plain-object "^3.0.0" node-fetch "^2.3.0" once "^1.4.0" - universal-user-agent "^3.0.0" + universal-user-agent "^4.0.0" "@octokit/rest@^16.28.4": - version "16.28.7" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.28.7.tgz#a2c2db5b318da84144beba82d19c1a9dbdb1a1fa" - integrity sha512-cznFSLEhh22XD3XeqJw51OLSfyL2fcFKUO+v2Ep9MTAFfFLS1cK1Zwd1yEgQJmJoDnj4/vv3+fGGZweG+xsbIA== + version "16.33.1" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.33.1.tgz#19229f5fd28d8e071644d37c249775ee40add433" + integrity sha512-lOQ+fJZwkeJ/1PRTdnY1uNja01aKOMioRhQfZtei64gZMXIX3EAfF4koMQMvoLFwsnVBu3ifj1JW1WAAKdXcnA== dependencies: - "@octokit/request" "^5.0.0" + "@octokit/request" "^5.2.0" "@octokit/request-error" "^1.0.2" atob-lite "^2.0.0" before-after-hook "^2.0.0" @@ -1526,8 +1486,7 @@ lodash.uniq "^4.5.0" octokit-pagination-methods "^1.1.0" once "^1.4.0" - universal-user-agent "^3.0.0" - url-template "^2.0.8" + universal-user-agent "^4.0.0" "@parcel/fs@^1.11.0": version "1.11.0" @@ -1538,10 +1497,10 @@ mkdirp "^0.5.1" rimraf "^2.6.2" -"@parcel/logger@^1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-1.11.0.tgz#91f39da14ba08dd85db247145698c62102960abb" - integrity sha512-lIRfDg+junbFUUeU0QtHX00gKCgEsYHZydFKwrJ8dc0D+WE2SYT1FcVCgpPAfKYgtg0QQMns8E9vzT9UjH92PQ== +"@parcel/logger@^1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-1.11.1.tgz#c55b0744bcbe84ebc291155627f0ec406a23e2e6" + integrity sha512-9NF3M6UVeP2udOBDILuoEHd8VrF4vQqoWHEafymO1pfSoOMfxrSJZw1MfyAAIUN/IFp9qjcpDCUbDZB+ioVevA== dependencies: "@parcel/workers" "^1.11.0" chalk "^2.1.0" @@ -1554,13 +1513,13 @@ resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-1.11.0.tgz#539e08fff8af3b26eca11302be80b522674b51ea" integrity sha512-cA3p4jTlaMeOtAKR/6AadanOPvKeg8VwgnHhOyfi0yClD0TZS/hi9xu12w4EzA/8NtHu0g6o4RDfcNjqN8l1AQ== -"@parcel/watcher@^1.12.0": - version "1.12.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-1.12.0.tgz#769024b2a810b0c3b38c310f297d104c77df3660" - integrity sha512-yijGiAqG7Tjf5WnFwOkiNWwerfZQDNABldiiqRDtr7vDWLO+F/DIncyB7tTcaD5Loevrr5mzzGo8Ntf3d2GIPg== +"@parcel/watcher@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-1.12.1.tgz#b98b3df309fcab93451b5583fc38e40826696dad" + integrity sha512-od+uCtCxC/KoNQAIE1vWx1YTyKYY+7CTrxBJPRh3cDWw/C0tCtlBMVlrbplscGoEpt6B27KhJDCv82PBxOERNA== dependencies: "@parcel/utils" "^1.11.0" - chokidar "^2.0.3" + chokidar "^2.1.5" "@parcel/workers@^1.11.0": version "1.11.0" @@ -1570,7 +1529,12 @@ "@parcel/utils" "^1.11.0" physical-cpu-count "^2.0.0" -"@types/estree@0.0.39": +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + +"@types/estree@*": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== @@ -1580,14 +1544,7 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== -"@types/fs-extra@^5.0.3": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.1.0.tgz#2a325ef97901504a3828718c390d34b8426a10a1" - integrity sha512-AInn5+UBFIK9FK5xc9yP5e3TQSPNNgjHByqYcj9g5elVBnDQcQL7PlO1CIRy2gWlbwK7UPYqi7vRvFA44dCmYQ== - dependencies: - "@types/node" "*" - -"@types/glob@*", "@types/glob@^7.1.1": +"@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== @@ -1596,28 +1553,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/handlebars@^4.0.38": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.1.0.tgz#3fcce9bf88f85fe73dc932240ab3fb682c624850" - integrity sha512-gq9YweFKNNB1uFK71eRqsd4niVkXrxHugqWFQkeLRJvGjnxsLr16bYtcsG4tOFwmYi0Bax+wCkbf1reUfdl4kA== - dependencies: - handlebars "*" - -"@types/highlight.js@^9.12.3": - version "9.12.3" - resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca" - integrity sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ== - -"@types/lodash@^4.14.110": - version "4.14.136" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.136.tgz#413e85089046b865d960c9ff1d400e04c31ab60f" - integrity sha512-0GJhzBdvsW2RUccNHOBkabI8HZVdOXmXbXhuKlDEd5Vv12P7oAVGfomGp3Ne21o5D/qu1WmthlNKFaoZJJeErA== - -"@types/marked@^0.4.0": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.4.2.tgz#64a89e53ea37f61cc0f3ee1732c555c2dbf6452f" - integrity sha512-cDB930/7MbzaGF6U3IwSQp6XBru8xWajF5PV2YZZeV8DyiliTuld11afVztGI9+yJZ29il5E+NpGA6ooV/Cjkg== - "@types/minimatch@*", "@types/minimatch@3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -1628,29 +1563,16 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== -"@types/node@*", "@types/node@^12.6.2", "@types/node@^12.6.3": - version "12.6.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.8.tgz#e469b4bf9d1c9832aee4907ba8a051494357c12c" - integrity sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg== +"@types/node@*", "@types/node@^12.6.3": + version "12.7.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.12.tgz#7c6c571cc2f3f3ac4a59a5f2bd48f5bdbc8653cc" + integrity sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ== "@types/q@^1.5.1": version "1.5.2" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== -"@types/shelljs@^0.8.0": - version "0.8.5" - resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.5.tgz#1e507b2f6d1f893269bd3e851ec24419ef9beeea" - integrity sha512-bZgjwIWu9gHCjirKJoOlLzGi5N0QgZ5t7EXEuoqyWCHTuSddURXo3FOBYDyRPNOWzZ6NbkLvZnVkn483Y/tvcQ== - dependencies: - "@types/glob" "*" - "@types/node" "*" - -"@types/webgl2@^0.0.5": - version "0.0.5" - resolved "https://registry.yarnpkg.com/@types/webgl2/-/webgl2-0.0.5.tgz#dd925e20ab8ace80eb4b1e46fda5b109c508fb0d" - integrity sha512-oGaKsBbxQOY5+aJFV3KECDhGaXt+yZJt2y/OZsnQGLRkH6Fvr7rv4pCt3SRH1somIHfej/c4u7NSpCyd9x+1Ow== - "@webassemblyjs/ast@1.8.5": version "1.8.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" @@ -1825,19 +1747,19 @@ JSONStream@^1.0.4, JSONStream@^1.3.4: through ">=2.2.7 <3" abab@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" - integrity sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w== + version "2.0.2" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.2.tgz#a2fba1b122c69a85caa02d10f9270c7219709a9d" + integrity sha512-2scffjvioEmNz0OyDSLGWDfKCVwaKc6l9Pm9kOIREU13ClXZvHpg/nRL5xyjSSSLhOnXqft2HpsAzNEEA8cFFg== abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -acorn-globals@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.2.tgz#4e2c2313a597fd589720395f6354b41cd5ec8006" - integrity sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ== +acorn-globals@^4.3.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" + integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== dependencies: acorn "^6.0.1" acorn-walk "^6.0.1" @@ -1852,10 +1774,15 @@ acorn@^5.0.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== -acorn@^6.0.1, acorn@^6.1.1, acorn@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.1.tgz#3ed8422d6dec09e6121cc7a843ca86a330a86b51" - integrity sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q== +acorn@^6.0.1, acorn@^6.0.4, acorn@^6.2.1: + version "6.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" + integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== + +acorn@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c" + integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ== agent-base@4, agent-base@^4.3.0: version "4.3.0" @@ -1883,12 +1810,12 @@ ajv-errors@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== -ajv-keywords@^3.1.0: +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== -ajv@^6.1.0, ajv@^6.5.5: +ajv@^6.1.0, ajv@^6.10.2, ajv@^6.5.5: version "6.10.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== @@ -1941,11 +1868,11 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: color-convert "^1.9.0" ansi-to-html@^0.6.4: - version "0.6.11" - resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.11.tgz#5093fc4962186c0e9343dec572a4f71abdc93439" - integrity sha512-88XZtrcwrfkyn6fGstHnkaF1kl7hGtNCYh4vSmItgEV+6JnQHryDBf7udF4f2RhTRQmYvJvPcTtqgaqrxzc9oA== + version "0.6.12" + resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.12.tgz#9dcd1646f17770d02ec065615e97f979f4e313cb" + integrity sha512-qBkIqLW979675mP76yB7yVkzeAWtATegdnDQ0RA3CZzknx0yUlNxMSML4xFdBfTs2GWYFQ1FELfbGbVSPzJ+LA== dependencies: - entities "^1.1.1" + entities "^1.1.2" any-promise@^1.0.0: version "1.3.0" @@ -2075,6 +2002,17 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" +assemblyscript@AssemblyScript/assemblyscript: + version "0.7.0" + resolved "https://codeload.github.com/AssemblyScript/assemblyscript/tar.gz/c7740fe36590679c411e4de1c0865732f8050c03" + dependencies: + "@protobufjs/utf8" "^1.1.0" + binaryen "89.0.0-nightly.20191012" + glob "^7.1.4" + long "^4.0.0" + opencollective-postinstall "^2.0.0" + source-map-support "^0.5.13" + assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" @@ -2098,10 +2036,10 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async-limiter@^1.0.0, async-limiter@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" - integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== asynckit@^0.4.0: version "0.4.0" @@ -2162,15 +2100,22 @@ babylon-walk@^1.0.2: babel-types "^6.15.0" lodash.clone "^4.5.0" +backbone@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12" + integrity sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ== + dependencies: + underscore ">=1.8.3" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= base64-js@^1.0.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" - integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== base@^0.11.1: version "0.11.2" @@ -2215,15 +2160,20 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== +binaryen@89.0.0-nightly.20191012: + version "89.0.0-nightly.20191012" + resolved "https://registry.yarnpkg.com/binaryen/-/binaryen-89.0.0-nightly.20191012.tgz#4079f4399cd3961f876aa1c913c0c9546f1c23f8" + integrity sha512-ipKFZEyrMnFuEPCzEsXZ80DwZhGzrPldQ+vtcyCfdr8O75PKawfll1gmdWg4sUtzjSnfdvK9NiYfzm021lDNUg== + bindings@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11" integrity sha1-FK1hE4EtLTfXLme0ystLtyZQXxE= bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: - version "3.5.5" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" - integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== + version "3.7.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.0.tgz#56a6a886e03f6ae577cffedeb524f8f2450293cf" + integrity sha512-aBQ1FxIa7kSWCcmKHlcHFlT2jt6J/l4FzC7KcPELkOJOsPOb/bccdhmIrKDfXhwFrmc7vDoDrrepFvGqjyXGJg== bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" @@ -2350,14 +2300,14 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.0.0, browserslist@^4.1.0, browserslist@^4.3.4: - version "4.6.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.6.tgz#6e4bf467cde520bc9dbdf3747dafa03531cec453" - integrity sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA== +browserslist@^4.0.0, browserslist@^4.1.0, browserslist@^4.6.0, browserslist@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.0.tgz#9ee89225ffc07db03409f2fee524dc8227458a17" + integrity sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA== dependencies: - caniuse-lite "^1.0.30000984" - electron-to-chromium "^1.3.191" - node-releases "^1.1.25" + caniuse-lite "^1.0.30000989" + electron-to-chromium "^1.3.247" + node-releases "^1.1.29" btoa-lite@^1.0.0: version "1.0.0" @@ -2413,30 +2363,10 @@ byte-size@^5.0.1: resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-5.0.1.tgz#4b651039a5ecd96767e71a3d7ed380e48bed4191" integrity sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw== -cacache@^11.3.2: - version "11.3.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.3.tgz#8bd29df8c6a718a6ebd2d010da4d7972ae3bbadc" - integrity sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cacache@^12.0.0: - version "12.0.2" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.2.tgz#8db03205e36089a3df6954c66ce92541441ac46c" - integrity sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg== +cacache@^12.0.0, cacache@^12.0.2, cacache@^12.0.3: + version "12.0.3" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" + integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw== dependencies: bluebird "^3.5.5" chownr "^1.1.1" @@ -2545,10 +2475,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000984: - version "1.0.30000986" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000986.tgz#f34350e367cc900509511574817ac092112bf7ab" - integrity sha512-pM+LnkoAX0+QnIH3tpW5EnkmfpEoqOD8FAcoBvsl3Xh6DXkgctiCxeCbXphP/k3XJtJzm+zOAJbi6U6IVkpWZQ== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000989: + version "1.0.30000999" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz#427253a69ad7bea4aa8d8345687b8eec51ca0e43" + integrity sha512-1CUyKyecPeksKwXZvYw0tEoaMCo/RwBlXmEtN5vVnabvO0KPd9RQLcaAuR9/1F+KDMv6esmOFWlsXuzDk+8rxg== caseless@~0.12.0: version "0.12.0" @@ -2580,10 +2510,10 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chokidar@^2.0.2, chokidar@^2.0.3: - version "2.1.6" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5" - integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g== +chokidar@^2.0.2, chokidar@^2.1.5: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== dependencies: anymatch "^2.0.0" async-each "^1.0.1" @@ -2599,12 +2529,12 @@ chokidar@^2.0.2, chokidar@^2.0.3: optionalDependencies: fsevents "^1.2.7" -chownr@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" - integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== +chownr@^1.1.1, chownr@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" + integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== -chrome-trace-event@^1.0.0: +chrome-trace-event@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== @@ -2679,11 +2609,6 @@ clone@^2.1.1: resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= -clones@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/clones/-/clones-1.2.0.tgz#b34c872045446a9f264ccceb7731bca05c529b71" - integrity sha512-FXDYw4TjR8wgPZYui2LeTqWh1BLpfQ8lB6upMtlpDF6WlOOxghmTTxWyngdKTgozqBgKnHbTVwTE+hOHqAykuQ== - coa@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" @@ -2759,11 +2684,16 @@ command-exists@^1.2.6: resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291" integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw== -commander@^2.11.0, commander@^2.12.1, commander@^2.19.0, commander@^2.20.0, commander@~2.20.0: +commander@2.20.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== +commander@^2.11.0, commander@^2.12.1, commander@^2.19.0, commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -2807,7 +2737,7 @@ concat-stream@^2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" -config-chain@^1.1.11, config-chain@^1.1.12: +config-chain@^1.1.11: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== @@ -2833,9 +2763,9 @@ constants-browserify@^1.0.0: integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= conventional-changelog-angular@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.3.tgz#299fdd43df5a1f095283ac16aeedfb0a682ecab0" - integrity sha512-YD1xzH7r9yXQte/HF9JBuEDfvjxxwDGGwZU1+ndanbY0oFgA+Po1T9JDSpPLdP0pZT6MhCAsdvFKC4TJ4MTJTA== + version "5.0.5" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.5.tgz#69b541bcf3e538a8578b1e5fbaabe9bd8f572b57" + integrity sha512-RrkdWnL/TVyWV1ayWmSsrWorsTDqjL/VwG5ZSEneBQrd65ONcfeA1cW7FLtNweQyMiKOyriCMTKRSlk18DjTrw== dependencies: compare-func "^1.3.1" q "^1.5.1" @@ -2865,14 +2795,14 @@ conventional-changelog-preset-loader@^2.1.1: integrity sha512-zXB+5vF7D5Y3Cb/rJfSyCCvFphCVmF8mFqOdncX3BmjZwAtGAPfYrBcT225udilCKvBbHgyzgxqz2GWDB5xShQ== conventional-changelog-writer@^4.0.6: - version "4.0.7" - resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.0.7.tgz#e4b7d9cbea902394ad671f67108a71fa90c7095f" - integrity sha512-p/wzs9eYaxhFbrmX/mCJNwJuvvHR+j4Fd0SQa2xyAhYed6KBiZ780LvoqUUvsayP4R1DtC27czalGUhKV2oabw== + version "4.0.9" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.0.9.tgz#44ac4c48121bc90e71cb2947e1ea1a6c222ccd7f" + integrity sha512-2Y3QfiAM37WvDMjkVNaRtZgxVzWKj73HE61YQ/95T53yle+CRwTVSl6Gbv/lWVKXeZcM5af9n9TDVf0k7Xh+cw== dependencies: compare-func "^1.3.1" conventional-commits-filter "^2.0.2" dateformat "^3.0.0" - handlebars "^4.1.2" + handlebars "^4.4.0" json-stringify-safe "^5.0.1" lodash "^4.2.1" meow "^4.0.0" @@ -2889,9 +2819,9 @@ conventional-commits-filter@^2.0.2: modify-values "^1.0.0" conventional-commits-parser@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.0.3.tgz#c3f972fd4e056aa8b9b4f5f3d0e540da18bf396d" - integrity sha512-KaA/2EeUkO4bKjinNfGUyqPTX/6w9JGshuQRik4r/wJz7rUw3+D3fDG6sZSEqJvKILzKXFQuFkpPLclcsAuZcg== + version "3.0.5" + resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.0.5.tgz#df471d6cb3f6fecfd1356ac72e0b577dbdae0a9c" + integrity sha512-qVz9+5JwdJzsbt7JbJ6P7NOXBGt8CyLFJYSjKAuPSgO+5UGfcsbk9EMR+lI8Unlvx6qwIc2YDJlrGIfay2ehNA== dependencies: JSONStream "^1.0.4" is-text-path "^2.0.0" @@ -2939,10 +2869,18 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -core-js@^2.4.0: - version "2.6.9" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" - integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== +core-js-compat@^3.1.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.3.2.tgz#1096c989c1b929ede06b5b6b4768dc4439078c03" + integrity sha512-gfiK4QnNXhnnHVOIZst2XHdFfdMTPxtR0EGs0TdILMlGIft+087oH6/Sw2xTTIjpWXC9vEwsJA8VG3XTGcmO5g== + dependencies: + browserslist "^4.7.0" + semver "^6.3.0" + +core-js@^2.4.0, core-js@^2.6.5: + version "2.6.10" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.10.tgz#8a5b8391f8cc7013da703411ce5b585706300d7f" + integrity sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -3202,12 +3140,12 @@ csso@^3.5.1: dependencies: css-tree "1.0.0-alpha.29" -cssom@0.3.x, cssom@^0.3.6: +cssom@0.3.x, cssom@^0.3.4: version "0.3.8" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== -cssstyle@^1.2.2: +cssstyle@^1.1.1: version "1.4.0" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== @@ -3221,10 +3159,10 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" -cyclist@~0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" - integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= +cyclist@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" + integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= dargs@^4.0.1: version "4.1.0" @@ -3333,11 +3271,6 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.0.0.tgz#3e3110ca29205f120d7cb064960a39c3d2087c09" - integrity sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww== - default-require-extensions@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" @@ -3437,11 +3370,16 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" -diff@3.5.0, diff@^3.2.0: +diff@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diff@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" + integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -3459,23 +3397,28 @@ dir-glob@^2.2.2: path-type "^3.0.0" dom-serializer@0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" - integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + version "0.2.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.1.tgz#13650c850daffea35d8b626a4cfc4d3a17643fdb" + integrity sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q== dependencies: - domelementtype "^1.3.0" - entities "^1.1.1" + domelementtype "^2.0.1" + entities "^2.0.0" domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: +domelementtype@1, domelementtype@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== +domelementtype@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" + integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== + domexception@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" @@ -3512,10 +3455,10 @@ dot-prop@^4.1.1, dot-prop@^4.2.0: dependencies: is-obj "^1.0.0" -dotenv-expand@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-4.2.0.tgz#def1f1ca5d6059d24a766e587942c21106ce1275" - integrity sha1-3vHxyl1gWdJKdm5YeULCEQbOEnU= +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== dotenv@^5.0.0: version "5.0.1" @@ -3552,30 +3495,20 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -editorconfig@^0.15.3: - version "0.15.3" - resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" - integrity sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g== - dependencies: - commander "^2.19.0" - lru-cache "^4.1.5" - semver "^5.6.0" - sigmund "^1.0.1" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.191: - version "1.3.204" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.204.tgz#1ea5c6d495bab77995aa135dbcbc1d383dd4e21e" - integrity sha512-T0eXE6hfbtpzRUaI7aHI/HYJ29Ndk84aVSborRAmXfWvBvz2EuB2OWYUxNcUX9d+jtqEIjgZjWMdoxS0hp5j1g== +electron-to-chromium@^1.3.247: + version "1.3.282" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.282.tgz#16118ae9c79a32ea93a17591d5b16e28d10fc08d" + integrity sha512-irSaDeCGgfMu1OA30bhqIBr+dx+pDJjRbwCpob7YWqVZbzXblybNzPGklVnWqv4EXxbkEAzQYqiNCqNTgu00lQ== elliptic@^6.0.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.0.tgz#2b8ed4c891b7de3200e14412a5b8248c7af505ca" - integrity sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg== + version "6.5.1" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.1.tgz#c380f5f909bf1b9b4428d028cd18d3b0efd6b52b" + integrity sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -3608,13 +3541,13 @@ encoding@^0.1.11: iconv-lite "~0.4.13" end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" - integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" -enhanced-resolve@4.1.0, enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: +enhanced-resolve@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== @@ -3623,16 +3556,35 @@ enhanced-resolve@4.1.0, enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: memory-fs "^0.4.0" tapable "^1.0.0" -entities@^1.1.1: +enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" + integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +entities@^1.1.1, entities@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== +entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" + integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== + env-paths@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA= +envinfo@^7.3.1: + version "7.4.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.4.0.tgz#bef4ece9e717423aaf0c3584651430b735ad6630" + integrity sha512-FdDfnWnCVjxTTpWE3d6Jgh5JDIA3Cw7LCgpM/pI7kK1ORkjaqI2r6NqQ+ln2j0dfpgxY00AWieSvtkiZQKIItA== + err-code@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" @@ -3653,16 +3605,20 @@ error-ex@^1.2.0, error-ex@^1.3.1: is-arrayish "^0.2.1" es-abstract@^1.12.0, es-abstract@^1.5.1: - version "1.13.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" - integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== + version "1.15.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.15.0.tgz#8884928ec7e40a79e3c9bc812d37d10c8b24cc57" + integrity sha512-bhkEqWJ2t2lMeaJDuk7okMkJWI/yqgH/EoGwpcvv0XW9RWQsRspI4wt6xuyuvMvvQE3gg/D9HXppgk21w78GyQ== dependencies: es-to-primitive "^1.2.0" function-bind "^1.1.1" has "^1.0.3" + has-symbols "^1.0.0" is-callable "^1.1.4" is-regex "^1.0.4" - object-keys "^1.0.12" + object-inspect "^1.6.0" + object-keys "^1.1.1" + string.prototype.trimleft "^2.1.0" + string.prototype.trimright "^2.1.0" es-to-primitive@^1.2.0: version "1.2.0" @@ -3700,10 +3656,10 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@^1.11.1, escodegen@^1.8.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510" - integrity sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw== +escodegen@^1.11.0, escodegen@^1.8.1: + version "1.12.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.12.0.tgz#f763daf840af172bb3a2b6dd7219c0e17f7ff541" + integrity sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg== dependencies: esprima "^3.1.3" estraverse "^4.2.0" @@ -3724,7 +3680,7 @@ escodegen@~1.9.0: optionalDependencies: source-map "~0.6.1" -eslint-scope@^4.0.0: +eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== @@ -3750,9 +3706,9 @@ esrecurse@^4.1.0: estraverse "^4.1.0" estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" - integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== estree-walker@^0.6.1: version "0.6.1" @@ -3760,9 +3716,9 @@ estree-walker@^0.6.1: integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== esutils@^2.0.0, esutils@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" - integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== etag@~1.8.1: version "1.8.1" @@ -3928,9 +3884,9 @@ figures@^2.0.0: escape-string-regexp "^1.0.5" file-loader@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.1.0.tgz#3a763391bc9502da7c59612fe348e38fc1980336" - integrity sha512-ajDk1nlByoalZAGR4b0H6oD+EGlWnyW1qbSxzaUc7RFiqmn+RbXQQRbTc72jsiUIlVusJ4Et58ltds8ZwTfnAw== + version "4.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.2.0.tgz#5fb124d2369d7075d70a9a5abecd12e60a95215e" + integrity sha512-+xZnaK5R8kBJrHK0/6HRlrKNamvVS5rjyuju+rnyxRGuwUJwpAMsVzUl5dz6rK8brkzjV6JpcFNjp6NqV0g1OQ== dependencies: loader-utils "^1.2.3" schema-utils "^2.0.0" @@ -3957,7 +3913,7 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: +find-cache-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== @@ -4065,15 +4021,6 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-extra@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" - integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -4084,11 +4031,11 @@ fs-extra@^8.1.0: universalify "^0.1.0" fs-minipass@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" - integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== dependencies: - minipass "^2.2.1" + minipass "^2.6.0" fs-write-stream-atomic@^1.0.8: version "1.0.10" @@ -4250,9 +4197,9 @@ glob-parent@^3.1.0: path-dirname "^1.0.0" glob-parent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954" - integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg== + version "5.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" + integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== dependencies: is-glob "^4.0.1" @@ -4341,9 +4288,9 @@ globby@^9.2.0: slash "^2.0.0" graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b" - integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg== + version "4.2.2" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" + integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== grapheme-breaker@^0.3.2: version "0.3.2" @@ -4366,10 +4313,10 @@ gzip-size@^5.1.0: duplexer "^0.1.1" pify "^4.0.1" -handlebars@*, handlebars@^4.0.6, handlebars@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" - integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw== +handlebars@^4.1.2, handlebars@^4.4.0: + version "4.4.3" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.4.3.tgz#180bae52c1d0e9ec0c15d7e82a4362d662762f6e" + integrity sha512-B0W4A2U1ww3q7VVthTKfh+epHx+q4mCt6iK+zEAzbMBpWQAwxCeKxEGpj/1oQTpzPXDNSOG7hmG14TsISH50yw== dependencies: neo-async "^2.6.0" optimist "^0.6.1" @@ -4488,10 +4435,10 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -highlight.js@^9.13.1: - version "9.15.8" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.8.tgz#f344fda123f36f1a65490e932cf90569e4999971" - integrity sha512-RrapkKQWwE+wKdF73VsOa2RQdIoO3mxwJ4P8mhbI6KYJUraUHRKM5w5zQQKXNk0xNL4UVRdulV9SBJcmzJNzVA== +highlight.js@^9.15.8: + version "9.15.10" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2" + integrity sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw== hmac-drbg@^1.0.0: version "1.0.1" @@ -4509,10 +4456,10 @@ homedir-polyfill@^1.0.1: dependencies: parse-passwd "^1.0.0" -hosted-git-info@^2.1.4, hosted-git-info@^2.6.0: - version "2.7.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" - integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== +hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: + version "2.8.5" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c" + integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg== hsl-regex@^1.0.0: version "1.0.0" @@ -4643,9 +4590,9 @@ iferr@^0.1.5: integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== + version "3.0.3" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" + integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== dependencies: minimatch "^3.0.4" @@ -4692,7 +4639,7 @@ indexes-of@^1.0.1: resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= -infer-owner@^1.0.3: +infer-owner@^1.0.3, infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== @@ -4740,9 +4687,9 @@ init-package-json@^1.10.3: validate-npm-package-name "^3.0.0" inquirer@^6.2.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42" - integrity sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA== + version "6.5.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" + integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== dependencies: ansi-escapes "^3.2.0" chalk "^2.4.2" @@ -4775,11 +4722,6 @@ invert-kv@^2.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= - ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -4790,10 +4732,10 @@ is-absolute-url@^2.0.0: resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= -is-absolute-url@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.0.tgz#eb21d69df2ed8ef72a3e6f243e216563036a0913" - integrity sha512-3OkP8XrM2Xq4/IxsJnClfMp3OaM3TAatLPLKPeWcxLBTrpe6hihwtX+XZfJTcXg/FTRi4qjy0y/C5qiyNxY24g== +is-absolute-url@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== is-accessor-descriptor@^0.1.6: version "0.1.6" @@ -4832,9 +4774,9 @@ is-buffer@^1.1.5: integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-buffer@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" - integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" + integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== is-callable@^1.1.4: version "1.1.4" @@ -5159,16 +5101,10 @@ istanbul-reports@^2.2.4: dependencies: handlebars "^4.1.2" -js-beautify@^1.8.9: - version "1.10.1" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.1.tgz#bdfe738ddbcaa12e4fced5af2d7cfad59f60ac0a" - integrity sha512-4y8SHOIRC+/YQ2gs3zJEKBUraQerq49FJYyXRpdzUGYQzCq8q9xtIh0YXial1S5KmonVui4aiUb6XaGyjE51XA== - dependencies: - config-chain "^1.1.12" - editorconfig "^0.15.3" - glob "^7.1.3" - mkdirp "~0.5.1" - nopt "~4.0.1" +jquery@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== js-cleanup@^1.0.1: version "1.0.1" @@ -5202,36 +5138,36 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^15.1.0: - version "15.1.1" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.1.1.tgz#21ed01f81d95ef4327f3e564662aef5e65881252" - integrity sha512-cQZRBB33arrDAeCrAEWn1U3SvrvC8XysBua9Oqg1yWrsY/gYcusloJC3RZJXuY5eehSCmws8f2YeliCqGSkrtQ== +jsdom@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-14.1.0.tgz#916463b6094956b0a6c1782c94e380cd30e1981b" + integrity sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng== dependencies: abab "^2.0.0" - acorn "^6.1.1" - acorn-globals "^4.3.2" + acorn "^6.0.4" + acorn-globals "^4.3.0" array-equal "^1.0.0" - cssom "^0.3.6" - cssstyle "^1.2.2" + cssom "^0.3.4" + cssstyle "^1.1.1" data-urls "^1.1.0" domexception "^1.0.1" - escodegen "^1.11.1" + escodegen "^1.11.0" html-encoding-sniffer "^1.0.2" - nwsapi "^2.1.4" + nwsapi "^2.1.3" parse5 "5.1.0" pn "^1.1.0" request "^2.88.0" - request-promise-native "^1.0.7" + request-promise-native "^1.0.5" saxes "^3.1.9" symbol-tree "^3.2.2" - tough-cookie "^3.0.1" + tough-cookie "^2.5.0" w3c-hr-time "^1.0.1" w3c-xmlserializer "^1.1.2" webidl-conversions "^4.0.2" whatwg-encoding "^1.0.5" whatwg-mimetype "^2.3.0" whatwg-url "^7.0.0" - ws "^7.0.0" + ws "^6.1.2" xml-name-validator "^3.0.0" jsesc@^2.5.1: @@ -5272,9 +5208,9 @@ json5@^1.0.1: minimist "^1.2.0" json5@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" - integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ== + version "2.1.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" + integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ== dependencies: minimist "^1.2.0" @@ -5332,25 +5268,25 @@ lcid@^2.0.0: invert-kv "^2.0.0" lerna@^3.16.4: - version "3.16.4" - resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.16.4.tgz#158cb4f478b680f46f871d5891f531f3a2cb31ec" - integrity sha512-0HfwXIkqe72lBLZcNO9NMRfylh5Ng1l8tETgYQ260ZdHRbPuaLKE3Wqnd2YYRRkWfwPyEyZO8mZweBR+slVe1A== + version "3.17.0" + resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.17.0.tgz#4bc99d1d9cc2a1d7732780af11b4a60c6a135333" + integrity sha512-rU3YU1MLN+yy1YZzhaEl8h50d8HtIeBKIQ/ZxmmTqDprIvvWT2AZyDt/9EvoWlirCfdKT/G6Hs63O6sFuDef9g== dependencies: - "@lerna/add" "3.16.2" - "@lerna/bootstrap" "3.16.2" - "@lerna/changed" "3.16.4" - "@lerna/clean" "3.16.0" + "@lerna/add" "3.17.0" + "@lerna/bootstrap" "3.17.0" + "@lerna/changed" "3.16.5" + "@lerna/clean" "3.16.5" "@lerna/cli" "3.13.0" - "@lerna/create" "3.16.0" - "@lerna/diff" "3.16.0" - "@lerna/exec" "3.16.0" - "@lerna/import" "3.16.0" - "@lerna/init" "3.16.0" - "@lerna/link" "3.16.2" - "@lerna/list" "3.16.0" - "@lerna/publish" "3.16.4" - "@lerna/run" "3.16.0" - "@lerna/version" "3.16.4" + "@lerna/create" "3.16.5" + "@lerna/diff" "3.16.5" + "@lerna/exec" "3.16.5" + "@lerna/import" "3.16.5" + "@lerna/init" "3.16.5" + "@lerna/link" "3.17.0" + "@lerna/list" "3.16.5" + "@lerna/publish" "3.16.5" + "@lerna/run" "3.16.5" + "@lerna/version" "3.16.5" import-local "^2.0.0" npmlog "^4.1.2" @@ -5394,12 +5330,12 @@ load-json-file@^5.3.0: strip-bom "^3.0.0" type-fest "^0.3.0" -loader-runner@^2.3.0: +loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3: +loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== @@ -5489,7 +5425,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.2.1: +lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.2.1: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -5501,6 +5437,11 @@ log-symbols@2.2.0, log-symbols@^2.2.0: dependencies: chalk "^2.0.1" +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + loose-envify@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -5516,7 +5457,7 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" -lru-cache@^4.0.1, lru-cache@^4.1.5: +lru-cache@^4.0.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== @@ -5531,6 +5472,11 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lunr@^2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.7.tgz#05ccf3af9d0e169b8f432c97e02fc1bdf3f36343" + integrity sha512-HjFSiy0Y0qZoW5OA1I6qBi7OnsDdqQnaUr03jhorh30maQoaP+4lQCKklYE3Nq3WJMSUfuBl6N+bKY5wxCb9hw== + macos-release@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" @@ -5544,9 +5490,9 @@ magic-string@^0.22.4: vlq "^0.2.2" magic-string@^0.25.1: - version "0.25.3" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.3.tgz#34b8d2a2c7fec9d9bdf9929a3fd81d271ef35be9" - integrity sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA== + version "0.25.4" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.4.tgz#325b8a0a79fc423db109b77fd5a19183b7ba5143" + integrity sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw== dependencies: sourcemap-codec "^1.4.4" @@ -5616,10 +5562,10 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -marked@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.4.0.tgz#9ad2c2a7a1791f10a852e0112f77b571dce10c66" - integrity sha512-tMsdNBgOsrUophCAFQl0XPe6Zqk/uy9gnue+jIIKhykO51hxyu6uNx7zBPy0+y/WKYVZZMspV9YeXLNdKk+iYw== +marked@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e" + integrity sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg== md5.js@^1.3.4: version "1.3.5" @@ -5649,7 +5595,7 @@ mem@^4.0.0: mimic-fn "^2.0.0" p-is-promise "^2.0.0" -memory-fs@^0.4.0, memory-fs@~0.4.1: +memory-fs@^0.4.0, memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -5657,6 +5603,14 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + meow@^3.3.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -5703,11 +5657,11 @@ merge-source-map@^1.1.0: source-map "^0.6.1" merge2@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5" - integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA== + version "1.3.0" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" + integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== -micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -5809,20 +5763,20 @@ minimist@~0.0.1: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= -minipass@^2.2.1, minipass@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== +minipass@^2.3.5, minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== dependencies: safe-buffer "^5.1.2" yallist "^3.0.0" minizlib@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== dependencies: - minipass "^2.2.1" + minipass "^2.9.0" mississippi@^3.0.0: version "3.0.0" @@ -5855,7 +5809,7 @@ mkdirp-promise@^5.0.1: dependencies: mkdirp "*" -mkdirp@*, mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@*, mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= @@ -5863,9 +5817,9 @@ mkdirp@*, mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5 minimist "0.0.8" mocha@^6.1.4: - version "6.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.2.0.tgz#f896b642843445d1bb8bca60eabd9206b8916e56" - integrity sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ== + version "6.2.1" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.2.1.tgz#da941c99437da9bac412097859ff99543969f94c" + integrity sha512-VCcWkLHwk79NYQc8cxhkmI8IigTIhsCwZ6RTxQsqK6go4UvEhzJkYuHm8B2YtlSxcYq2fY+ucr4JBwoD6ci80A== dependencies: ansi-colors "3.2.3" browser-stdout "1.3.1" @@ -5887,9 +5841,9 @@ mocha@^6.1.4: supports-color "6.0.0" which "1.3.1" wide-align "1.1.3" - yargs "13.2.2" - yargs-parser "13.0.0" - yargs-unparser "1.5.0" + yargs "13.3.0" + yargs-parser "13.1.1" + yargs-unparser "1.6.0" modify-values@^1.0.0: version "1.0.1" @@ -5983,7 +5937,7 @@ needle@^2.2.1: iconv-lite "^0.4.4" sax "^1.2.4" -neo-async@^2.5.0, neo-async@^2.6.0: +neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== @@ -6031,9 +5985,9 @@ node-forge@^0.7.1: integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== node-gyp@^5.0.2: - version "5.0.3" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.0.3.tgz#80d64c23790244991b6d44532f0a351bedd3dd45" - integrity sha512-z/JdtkFGUm0QaQUusvloyYuGDub3nUbOo5de1Fz57cM++osBTvQatBUSTlF1k/w8vFHPxxXW6zxGvkxXSpaBkQ== + version "5.0.5" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.0.5.tgz#f6cf1da246eb8c42b097d7cd4d6c3ce23a4163af" + integrity sha512-WABl9s4/mqQdZneZHVWVG4TVr6QQJZUC6PAx47ITSk9lreZ1n+7Z9mMAIbA3vnO4J9W20P7LhCxtzfWsAD/KDw== dependencies: env-paths "^1.0.0" glob "^7.0.3" @@ -6044,10 +5998,10 @@ node-gyp@^5.0.2: request "^2.87.0" rimraf "2" semver "~5.3.0" - tar "^4.4.8" + tar "^4.4.12" which "1" -node-libs-browser@^2.0.0: +node-libs-browser@^2.0.0, node-libs-browser@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== @@ -6092,12 +6046,12 @@ node-pre-gyp@^0.12.0: semver "^5.3.0" tar "^4" -node-releases@^1.1.25: - version "1.1.26" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.26.tgz#f30563edc5c7dc20cf524cc8652ffa7be0762937" - integrity sha512-fZPsuhhUHMTlfkhDLGtfY80DSJTjOcx+qD1j5pqPkuhUHVS7xHZIg9EE4DHK8O3f0zTxXHX5VIkDG8pu98/wfQ== +node-releases@^1.1.29: + version "1.1.35" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.35.tgz#32a74a3cd497aa77f23d509f483475fd160e4c48" + integrity sha512-JGcM/wndCN/2elJlU0IGdVEJQQnJwsLbgPCFd2pY7V0mxf17bZ0Gb/lgOtL29ZQhvEX5shnVhxQyZz3ex94N8w== dependencies: - semver "^5.3.0" + semver "^6.3.0" "nopt@2 || 3": version "3.0.6" @@ -6106,7 +6060,7 @@ node-releases@^1.1.25: dependencies: abbrev "1" -nopt@^4.0.1, nopt@~4.0.1: +nopt@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= @@ -6152,9 +6106,9 @@ npm-bundled@^1.0.1: integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== npm-lifecycle@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-3.1.2.tgz#06f2253ea3b9e122ce3e55e3496670a810afcc84" - integrity sha512-nhfOcoTHrW1lJJlM2o77vTE2RWR4YOVyj7YzmY0y5itsMjEuoJHteio/ez0BliENEPsNxIUQgwhyEW9dShj3Ww== + version "3.1.4" + resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-3.1.4.tgz#de6975c7d8df65f5150db110b57cce498b0b604c" + integrity sha512-tgs1PaucZwkxECGKhC/stbEgFyc3TGh2TJcg2CDr6jbvQRdteHNhmMeljRzpe4wgFAXQADoy1cSqqi7mtiAa5A== dependencies: byline "^5.0.0" graceful-fs "^4.1.15" @@ -6166,27 +6120,27 @@ npm-lifecycle@^3.1.2: which "^1.3.1" "npm-package-arg@^4.0.0 || ^5.0.0 || ^6.0.0", npm-package-arg@^6.0.0, npm-package-arg@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.0.tgz#15ae1e2758a5027efb4c250554b85a737db7fcc1" - integrity sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA== + version "6.1.1" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.1.tgz#02168cb0a49a2b75bf988a28698de7b529df5cb7" + integrity sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg== dependencies: - hosted-git-info "^2.6.0" + hosted-git-info "^2.7.1" osenv "^0.1.5" - semver "^5.5.0" + semver "^5.6.0" validate-npm-package-name "^3.0.0" npm-packlist@^1.1.6, npm-packlist@^1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44" - integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw== + version "1.4.6" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.6.tgz#53ba3ed11f8523079f1457376dd379ee4ea42ff4" + integrity sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg== dependencies: ignore-walk "^3.0.1" npm-bundled "^1.0.1" -npm-pick-manifest@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz#32111d2a9562638bb2c8f2bf27f7f3092c8fae40" - integrity sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA== +npm-pick-manifest@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz#f4d9e5fd4be2153e5f4e5f9b7be8dc419a99abb7" + integrity sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw== dependencies: figgy-pudding "^3.5.1" npm-package-arg "^6.0.0" @@ -6221,7 +6175,7 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -nwsapi@^2.1.4: +nwsapi@^2.1.3: version "2.1.4" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.4.tgz#e006a878db23636f8e8a67d33ca0e4edf61a842f" integrity sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw== @@ -6276,12 +6230,17 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-inspect@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" + integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== + object-inspect@~1.4.0: version "1.4.1" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.4.1.tgz#37ffb10e71adaf3748d05f713b4c9452f402cbc4" integrity sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw== -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6: +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -6354,6 +6313,11 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" +opencollective-postinstall@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89" + integrity sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw== + opn@^5.1.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" @@ -6412,7 +6376,7 @@ os-locale@^3.0.0, os-locale@^3.1.0: lcid "^2.0.0" mem "^4.0.0" -os-name@^3.0.0: +os-name@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== @@ -6456,9 +6420,9 @@ p-limit@^1.1.0: p-try "^1.0.0" p-limit@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" - integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== + version "2.2.1" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537" + integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg== dependencies: p-try "^2.0.0" @@ -6543,36 +6507,36 @@ pako@~1.0.5: integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw== parallel-transform@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06" - integrity sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY= + version "1.2.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" + integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== dependencies: - cyclist "~0.2.2" + cyclist "^1.0.1" inherits "^2.0.3" readable-stream "^2.1.5" -parcel-bundler@^1.12.3: - version "1.12.3" - resolved "https://registry.yarnpkg.com/parcel-bundler/-/parcel-bundler-1.12.3.tgz#2bbf70bfa2d06097f071653285040bd125684d09" - integrity sha512-8bq6lj0hhQeGxD9f9xEkFMXQ3d8TIlf2+isKxoi9bciB0KVEILRGllaPkUgp++5t0anToBh9+tG6ZyInXOC1/A== - dependencies: - "@babel/code-frame" "^7.0.0 <7.4.0" - "@babel/core" "^7.0.0 <7.4.0" - "@babel/generator" "^7.0.0 <7.4.0" - "@babel/parser" "^7.0.0 <7.4.0" - "@babel/plugin-transform-flow-strip-types" "^7.0.0 <7.4.0" - "@babel/plugin-transform-modules-commonjs" "^7.0.0 <7.4.0" - "@babel/plugin-transform-react-jsx" "^7.0.0 <7.4.0" - "@babel/preset-env" "^7.0.0 <7.4.0" - "@babel/runtime" "^7.0.0 <7.4.0" - "@babel/template" "^7.0.0 <7.4.0" - "@babel/traverse" "^7.0.0 <7.4.0" - "@babel/types" "^7.0.0 <7.4.0" +parcel-bundler@^1.12.4: + version "1.12.4" + resolved "https://registry.yarnpkg.com/parcel-bundler/-/parcel-bundler-1.12.4.tgz#31223f4ab4d00323a109fce28d5e46775409a9ee" + integrity sha512-G+iZGGiPEXcRzw0fiRxWYCKxdt/F7l9a0xkiU4XbcVRJCSlBnioWEwJMutOCCpoQmaQtjB4RBHDGIHN85AIhLQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/core" "^7.4.4" + "@babel/generator" "^7.4.4" + "@babel/parser" "^7.4.4" + "@babel/plugin-transform-flow-strip-types" "^7.4.4" + "@babel/plugin-transform-modules-commonjs" "^7.4.4" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/preset-env" "^7.4.4" + "@babel/runtime" "^7.4.4" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.4" + "@babel/types" "^7.4.4" "@iarna/toml" "^2.2.0" "@parcel/fs" "^1.11.0" - "@parcel/logger" "^1.11.0" + "@parcel/logger" "^1.11.1" "@parcel/utils" "^1.11.0" - "@parcel/watcher" "^1.12.0" + "@parcel/watcher" "^1.12.1" "@parcel/workers" "^1.11.0" ansi-to-html "^0.6.4" babylon-walk "^1.0.2" @@ -6581,12 +6545,14 @@ parcel-bundler@^1.12.3: clone "^2.1.1" command-exists "^1.2.6" commander "^2.11.0" + core-js "^2.6.5" cross-spawn "^6.0.4" css-modules-loader-core "^1.1.0" cssnano "^4.0.0" deasync "^0.1.14" dotenv "^5.0.0" - dotenv-expand "^4.2.0" + dotenv-expand "^5.1.0" + envinfo "^7.3.1" fast-glob "^2.2.2" filesize "^3.6.0" get-port "^3.2.0" @@ -6607,7 +6573,7 @@ parcel-bundler@^1.12.3: posthtml-render "^1.1.3" resolve "^1.4.0" semver "^5.4.1" - serialize-to-js "^1.1.1" + serialize-to-js "^3.0.0" serve-static "^1.12.4" source-map "0.6.1" terser "^3.7.3" @@ -6615,9 +6581,9 @@ parcel-bundler@^1.12.3: ws "^5.1.1" parse-asn1@^5.0.0: - version "5.1.4" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc" - integrity sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw== + version "5.1.5" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" + integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== dependencies: asn1.js "^4.0.0" browserify-aes "^1.0.0" @@ -7151,10 +7117,10 @@ postcss@^6.0.1: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.16, postcss@^7.0.5: - version "7.0.17" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f" - integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ== +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.17, postcss@^7.0.5: + version "7.0.18" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233" + integrity sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g== dependencies: chalk "^2.4.2" source-map "^0.6.1" @@ -7174,9 +7140,9 @@ posthtml-render@^1.1.3, posthtml-render@^1.1.5: integrity sha512-yvt54j0zCBHQVEFAuR+yHld8CZrCa/E1Z/OcFNCV1IEWTLVxT8O7nYnM4IIw1CD4r8kaRd3lc42+0lgCKgm87w== posthtml@^0.11.2, posthtml@^0.11.4: - version "0.11.4" - resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.11.4.tgz#26784d005d57d7aea93ab06dda899d59bdf186c7" - integrity sha512-ezlzBkoPoRgh0jkmT1dsM8eT+lr2azyZ546kbda8oHnVnzvyaB3Ywo6UxUz8wPSOkFAAflCxZJhvvpQH1F6qcA== + version "0.11.6" + resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.11.6.tgz#e349d51af7929d0683b9d8c3abd8166beecc90a8" + integrity sha512-C2hrAPzmRdpuL3iH0TDdQ6XCc9M7Dcc3zEW5BLerY65G4tWWszwv6nG/ksi6ul5i2mx22ubdljgktXCtNkydkw== dependencies: posthtml-parser "^0.4.1" posthtml-render "^1.1.5" @@ -7201,7 +7167,7 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -progress@^2.0.0: +progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== @@ -7254,9 +7220,9 @@ pseudomap@^1.0.2: integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= psl@^1.1.24, psl@^1.1.28: - version "1.2.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.2.0.tgz#df12b5b1b3a30f51c329eacbdef98f3a6e136dc6" - integrity sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA== + version "1.4.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2" + integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw== public-encrypt@^4.0.0: version "4.0.3" @@ -7375,16 +7341,16 @@ rc@^1.2.7: strip-json-comments "~2.0.1" read-cmd-shim@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz#2d5d157786a37c055d22077c32c53f8329e91c7b" - integrity sha1-LV0Vd4ajfAVdIgd8MsU/gynpHHs= + version "1.0.4" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.4.tgz#b4a53d43376211b45243f0072b6e603a8e37640d" + integrity sha512-Pqpl3qJ/QdOIjRYA0q5DND/gLvGOfpIz/fYVDGYpOXfW/lFrIttmLsBnd6IkyK10+JHU9zhsaudfvrQTBB9YFQ== dependencies: graceful-fs "^4.1.2" "read-package-json@1 || 2", read-package-json@^2.0.0, read-package-json@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.0.13.tgz#2e82ebd9f613baa6d2ebe3aa72cefe3f68e41f4a" - integrity sha512-/1dZ7TRZvGrYqE0UAfN6qQb5GYBsNcqS1C0tNK601CFOJmtHI7NIGXwetEPU/OtoFHZL3hDxm4rolFFVE9Bnmg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.0.tgz#e3d42e6c35ea5ae820d9a03ab0c7291217fc51d5" + integrity sha512-KLhu8M1ZZNkMcrq1+0UJbR8Dii8KZUqB0Sha4mOx/bknfKI/fyrQVrG/YIt2UOtG667sD8+ee4EXMM91W9dC+A== dependencies: glob "^7.1.1" json-parse-better-errors "^1.0.1" @@ -7515,7 +7481,7 @@ redent@^2.0.0: indent-string "^3.0.0" strip-indent "^2.0.0" -regenerate-unicode-properties@^8.0.2: +regenerate-unicode-properties@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA== @@ -7532,10 +7498,10 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.12.0: - version "0.12.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" - integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== +regenerator-runtime@^0.13.2: + version "0.13.3" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" + integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== regenerator-transform@^0.14.0: version "0.14.1" @@ -7552,11 +7518,6 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp-tree@^0.1.6: - version "0.1.11" - resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.11.tgz#c9c7f00fcf722e0a56c7390983a7a63dd6c272f3" - integrity sha512-7/l/DgapVVDzZobwMCCgMlqiqyLFJ0cduo/j+3BcDJIB+yJdsYCfKuI3l/04NV+H/rfNRdPIDbXNZHM9XvQatg== - regexpu-core@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" @@ -7566,13 +7527,13 @@ regexpu-core@^1.0.0: regjsgen "^0.2.0" regjsparser "^0.1.4" -regexpu-core@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae" - integrity sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ== +regexpu-core@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6" + integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg== dependencies: regenerate "^1.4.0" - regenerate-unicode-properties "^8.0.2" + regenerate-unicode-properties "^8.1.0" regjsgen "^0.5.0" regjsparser "^0.6.0" unicode-match-property-ecmascript "^1.0.4" @@ -7638,7 +7599,7 @@ request-promise-core@1.1.2: dependencies: lodash "^4.17.11" -request-promise-native@^1.0.7: +request-promise-native@^1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.7.tgz#a49868a624bdea5069f1251d0a836e0d89aa2c59" integrity sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w== @@ -7719,9 +7680,9 @@ resolve-url@^0.2.1: integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= resolve@^1.1.5, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0: - version "1.11.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" - integrity sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw== + version "1.12.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" + integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== dependencies: path-parse "^1.0.6" @@ -7754,9 +7715,9 @@ rgba-regex@^1.0.0: integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= rimraf@2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" @@ -7777,20 +7738,20 @@ rollup-plugin-cleanup@^3.1.1: rollup-pluginutils "^2.3.3" rollup-pluginutils@^2.3.3: - version "2.8.1" - resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz#8fa6dd0697344938ef26c2c09d2488ce9e33ce97" - integrity sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg== + version "2.8.2" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" + integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== dependencies: estree-walker "^0.6.1" rollup@^1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.17.0.tgz#47ee8b04514544fc93b39bae06271244c8db7dfa" - integrity sha512-k/j1m0NIsI4SYgCJR4MWPstGJOWfJyd6gycKoMhyoKPVXxm+L49XtbUwZyFsrSU2YXsOkM4u1ll9CS/ZgJBUpw== + version "1.23.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.23.1.tgz#0315a0f5d0dfb056e6363e1dff05b89ac2da6b8e" + integrity sha512-95C1GZQpr/NIA0kMUQmSjuMDQ45oZfPgDBcN0yZwBG7Kee//m7H68vgIyg+SPuyrTZ5PrXfyLK80OzXeKG5dAA== dependencies: - "@types/estree" "0.0.39" - "@types/node" "^12.6.2" - acorn "^6.2.0" + "@types/estree" "*" + "@types/node" "*" + acorn "^7.1.0" run-async@^2.2.0: version "2.3.0" @@ -7807,13 +7768,13 @@ run-queue@^1.0.0, run-queue@^1.0.3: aproba "^1.1.1" rxjs@^6.4.0: - version "6.5.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7" - integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg== + version "6.5.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" + integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA== dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== @@ -7835,13 +7796,6 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -safer-eval@^1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/safer-eval/-/safer-eval-1.3.5.tgz#a75a1193a4e1dfadd34a8c87ad08e7b978c494b1" - integrity sha512-BJ//K2Y+EgCbOHEsDGS5YahYBcYy7JcFpKDo2ba5t4MnOGHYtk7HvQkcxTDFvjQvJ0CRcdas/PyF+gTTCay+3w== - dependencies: - clones "^1.2.0" - sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -7864,19 +7818,19 @@ schema-utils@^1.0.0: ajv-keywords "^3.1.0" schema-utils@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.0.1.tgz#1eec2e059556af841b7f3a83b61af13d7a3f9196" - integrity sha512-HJFKJ4JixDpRur06QHwi8uu2kZbng318ahWEKgBjc0ZklcE4FDvmm2wghb448q0IRaABxIESt8vqPFvwgMB80A== + version "2.4.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.4.1.tgz#e89ade5d056dc8bcaca377574bb4a9c4e1b8be56" + integrity sha512-RqYLpkPZX5Oc3fw/kHHHyP56fg5Y+XBpIpV8nCg0znIALfq3OH+Ea9Hfeac9BAMwG5IICltiZ0vxFvJQONfA5w== dependencies: - ajv "^6.1.0" - ajv-keywords "^3.1.0" + ajv "^6.10.2" + ajv-keywords "^3.4.1" "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" - integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0, semver@^6.2.0: +semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -7906,17 +7860,14 @@ send@0.17.1: statuses "~1.5.0" serialize-javascript@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65" - integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA== + version "1.9.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb" + integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A== -serialize-to-js@^1.1.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/serialize-to-js/-/serialize-to-js-1.2.2.tgz#1a567b0c9bf557bc7d7b77b503dfae0a8218d15d" - integrity sha512-mUc8vA5iJghe+O+3s0YDGFLMJcqitVFk787YKiv8a4sf6RX5W0u81b+gcHrp15O0fFa010dRBVZvwcKXOWsL9Q== - dependencies: - js-beautify "^1.8.9" - safer-eval "^1.3.0" +serialize-to-js@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/serialize-to-js/-/serialize-to-js-3.0.0.tgz#1fd8736744819a4df29dc85e9d04a44a4984edc3" + integrity sha512-WdGgi0jGnWCQXph2p3vkxceDnTfvfyXfYxherQMRcZjSaJzMQdMBAW6i0nojsBKIZ3fFOztZKKVbbm05VbIdRA== serve-static@^1.12.4: version "1.14.1" @@ -7978,7 +7929,7 @@ shebang-regex@^1.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shelljs@^0.8.2: +shelljs@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A== @@ -7987,11 +7938,6 @@ shelljs@^0.8.2: interpret "^1.0.0" rechoir "^0.6.2" -sigmund@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" - integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= - signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -8098,10 +8044,10 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@~0.5.10, source-map-support@~0.5.12: - version "0.5.12" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" - integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== +source-map-support@^0.5.13, source-map-support@~0.5.10, source-map-support@~0.5.12: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -8127,9 +8073,9 @@ sourcemap-codec@^1.4.4: integrity sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg== spawn-wrap@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.4.2.tgz#cff58e73a8224617b6561abdc32586ea0c82248c" - integrity sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg== + version "1.4.3" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.4.3.tgz#81b7670e170cca247d80bf5faf0cfb713bdcf848" + integrity sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw== dependencies: foreground-child "^1.5.6" mkdirp "^0.5.0" @@ -8320,12 +8266,28 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string.prototype.trimleft@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634" + integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58" + integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" - integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: - safe-buffer "~5.1.0" + safe-buffer "~5.2.0" string_decoder@~1.1.1: version "1.1.1" @@ -8464,19 +8426,19 @@ symbol-tree@^3.2.2: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -tapable@^1.0.0, tapable@^1.1.0: +tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tar@^4, tar@^4.4.10, tar@^4.4.8: - version "4.4.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" - integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== +tar@^4, tar@^4.4.10, tar@^4.4.12, tar@^4.4.8: + version "4.4.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" + integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== dependencies: chownr "^1.1.1" fs-minipass "^1.2.5" - minipass "^2.3.5" + minipass "^2.8.6" minizlib "^1.2.1" mkdirp "^0.5.0" safe-buffer "^5.1.2" @@ -8499,20 +8461,19 @@ temp-write@^3.4.0: temp-dir "^1.0.0" uuid "^3.0.1" -terser-webpack-plugin@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz#69aa22426299f4b5b3775cbed8cb2c5d419aa1d4" - integrity sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg== +terser-webpack-plugin@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4" + integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg== dependencies: - cacache "^11.3.2" - find-cache-dir "^2.0.0" + cacache "^12.0.2" + find-cache-dir "^2.1.0" is-wsl "^1.1.0" - loader-utils "^1.2.3" schema-utils "^1.0.0" serialize-javascript "^1.7.0" source-map "^0.6.1" - terser "^4.0.0" - webpack-sources "^1.3.0" + terser "^4.1.2" + webpack-sources "^1.4.0" worker-farm "^1.7.0" terser@^3.7.3: @@ -8524,10 +8485,10 @@ terser@^3.7.3: source-map "~0.6.1" source-map-support "~0.5.10" -terser@^4.0.0, terser@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.1.2.tgz#b2656c8a506f7ce805a3f300a2ff48db022fa391" - integrity sha512-jvNoEQSPXJdssFwqPSgWjsOrb+ELoE+ILpHPKXC83tIxOlh2U75F1KuB2luLD/3a6/7K3Vw5pDn+hvu0C4AzSw== +terser@^4.1.2, terser@^4.3.8: + version "4.3.8" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.8.tgz#707f05f3f4c1c70c840e626addfdb1c158a17136" + integrity sha512-otmIRlRVmLChAWsnSFNO0Bfk6YySuBp6G9qrHiJwlLDd4mxe2ta4sjI7TzIR+W1nBMjilzrMcPOz9pSusgx3hQ== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -8583,9 +8544,9 @@ through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6: integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= timers-browserify@^2.0.4: - version "2.0.10" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" - integrity sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg== + version "2.0.11" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" + integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== dependencies: setimmediate "^1.0.4" @@ -8658,7 +8619,7 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -tough-cookie@^2.3.3: +tough-cookie@^2.3.3, tough-cookie@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -8666,15 +8627,6 @@ tough-cookie@^2.3.3: psl "^1.1.28" punycode "^2.1.1" -tough-cookie@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" - integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== - dependencies: - ip-regex "^2.1.0" - psl "^1.1.28" - punycode "^2.1.1" - tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -8705,15 +8657,10 @@ trim-off-newlines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= - ts-loader@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.0.4.tgz#bc331ad91a887a60632d94c9f79448666f2c4b63" - integrity sha512-p2zJYe7OtwR+49kv4gs7v4dMrfYD1IPpOtqiSPCbe8oR+4zEBtdHwzM7A7M91F+suReqgzZrlClk4LRSSp882g== + version "6.2.0" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.0.tgz#52d3993ecbc5474c1513242388e1049da0fce880" + integrity sha512-Da8h3fD+HiZ9GvZJydqzk3mTC9nuOKYlJcpuk+Zv6Y1DPaMvBL+56GRzZFypx2cWrZFMsQr869+Ua2slGoLxvQ== dependencies: chalk "^2.3.0" enhanced-resolve "^4.0.0" @@ -8727,15 +8674,15 @@ tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== tslint@^5.18.0: - version "5.18.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.18.0.tgz#f61a6ddcf372344ac5e41708095bbf043a147ac6" - integrity sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w== + version "5.20.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.0.tgz#fac93bfa79568a5a24e7be9cdde5e02b02d00ec1" + integrity sha512-2vqIvkMHbnx8acMogAERQ/IuINOq6DFqgF8/VDvhEkBqQh/x6SP0Y+OHnKth9/ZcHQSroOZwUQSN18v8KKF0/g== dependencies: "@babel/code-frame" "^7.0.0" builtin-modules "^1.1.1" chalk "^2.3.0" commander "^2.12.1" - diff "^3.2.0" + diff "^4.0.1" glob "^7.1.1" js-yaml "^3.13.1" minimatch "^3.0.4" @@ -8786,50 +8733,49 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typedoc-default-themes@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz#6dc2433e78ed8bea8e887a3acde2f31785bd6227" - integrity sha1-bcJDPnjti+qOiHo6zeLzF4W9Yic= - -typedoc@^0.14.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.14.2.tgz#769f457f4f9e4bdb8b5f3b177c86b6a31d8c3dc3" - integrity sha512-aEbgJXV8/KqaVhcedT7xG6d2r+mOvB5ep3eIz1KuB5sc4fDYXcepEEMdU7XSqLFO5hVPu0nllHi1QxX2h/QlpQ== - dependencies: - "@types/fs-extra" "^5.0.3" - "@types/handlebars" "^4.0.38" - "@types/highlight.js" "^9.12.3" - "@types/lodash" "^4.14.110" - "@types/marked" "^0.4.0" +typedoc-default-themes@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.6.0.tgz#7e73bf54dd9e11550dd0fb576d5176b758f8f8b5" + integrity sha512-MdTROOojxod78CEv22rIA69o7crMPLnVZPefuDLt/WepXqJwgiSu8Xxq+H36x0Jj3YGc7lOglI2vPJ2GhoOybw== + dependencies: + backbone "^1.4.0" + jquery "^3.4.1" + lunr "^2.3.6" + underscore "^1.9.1" + +typedoc@^0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.15.0.tgz#21eaf4db41cf2797bad027a74f2a75cd08ae0c2d" + integrity sha512-NOtfq5Tis4EFt+J2ozhVq9RCeUnfEYMFKoU6nCXCXUULJz1UQynOM+yH3TkfZCPLzigbqB0tQYGVlktUWweKlw== + dependencies: "@types/minimatch" "3.0.3" - "@types/shelljs" "^0.8.0" - fs-extra "^7.0.0" - handlebars "^4.0.6" - highlight.js "^9.13.1" - lodash "^4.17.10" - marked "^0.4.0" + fs-extra "^8.1.0" + handlebars "^4.1.2" + highlight.js "^9.15.8" + lodash "^4.17.15" + marked "^0.7.0" minimatch "^3.0.0" - progress "^2.0.0" - shelljs "^0.8.2" - typedoc-default-themes "^0.5.0" - typescript "3.2.x" + progress "^2.0.3" + shelljs "^0.8.3" + typedoc-default-themes "^0.6.0" + typescript "3.5.x" -typescript@3.2.x: - version "3.2.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.4.tgz#c585cb952912263d915b462726ce244ba510ef3d" - integrity sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg== - -typescript@^3.5.3: +typescript@3.5.x: version "3.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== +typescript@^3.6.4: + version "3.6.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d" + integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg== + uglify-js@^3.1.4: - version "3.6.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5" - integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg== + version "3.6.1" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.1.tgz#ae7688c50e1bdcf2f70a0e162410003cf9798311" + integrity sha512-+dSJLJpXBb6oMHP+Yvw8hUgElz4gLTh82XuX68QiJVTXaE5ibl6buzhNkQdYhBlIhozWOC9ge16wyRmjG4TwVQ== dependencies: - commander "~2.20.0" + commander "2.20.0" source-map "~0.6.1" uid-number@0.0.6: @@ -8843,20 +8789,25 @@ umask@^1.1.0: integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= uncss@^0.17.0: - version "0.17.0" - resolved "https://registry.yarnpkg.com/uncss/-/uncss-0.17.0.tgz#7d2682a6fffea2d90d001231dd151cc34c5a2b08" - integrity sha512-ZFFHCpAUEC0kJkuJelLYjrr0/bHBlgrN8XRDQN+wt9VVqap8Avj4fxt2WDJJZi0aSzYVaWCQoX19ZM+JZ5lqnw== + version "0.17.2" + resolved "https://registry.yarnpkg.com/uncss/-/uncss-0.17.2.tgz#fac1c2429be72108e8a47437c647d58cf9ea66f1" + integrity sha512-hu2HquwDItuGDem4YsJROdAD8SknmWtM24zwhQax6J1se8tPjV1cnwPKhtjodzBaUhaL8Zb3hlGdZ2WAUpbAOg== dependencies: commander "^2.20.0" glob "^7.1.4" - is-absolute-url "^3.0.0" + is-absolute-url "^3.0.1" is-html "^1.1.0" - jsdom "^15.1.0" - lodash "^4.17.11" - postcss "^7.0.16" + jsdom "^14.1.0" + lodash "^4.17.15" + postcss "^7.0.17" postcss-selector-parser "6.0.2" request "^2.88.0" +underscore@>=1.8.3, underscore@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" + integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -8922,12 +8873,12 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -universal-user-agent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-3.0.0.tgz#4cc88d68097bffd7ac42e3b7c903e7481424b4b9" - integrity sha512-T3siHThqoj5X0benA5H0qcDnrKGXzU8TKoX15x/tQHw1hQBvIEBHjxQ2klizYsqBOO/Q+WuxoQUihadeeqDnoA== +universal-user-agent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-4.0.0.tgz#27da2ec87e32769619f68a14996465ea1cb9df16" + integrity sha512-eM8knLpev67iBDizr/YtqkJsF3GK8gzDc6st/WKzrTuPtcsOKW/0IdL4cnMBsU69pOx0otavLWBDGTwg+dB0aA== dependencies: - os-name "^3.0.0" + os-name "^3.1.0" universalify@^0.1.0: version "0.1.2" @@ -8948,9 +8899,9 @@ unset-value@^1.0.0: isobject "^3.0.0" upath@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" - integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== uri-js@^4.2.2: version "4.2.2" @@ -8964,11 +8915,6 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url-template@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" - integrity sha1-/FZaPMy/93MMd19WQflVV5FDnyE= - url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -9017,15 +8963,20 @@ util@^0.11.0: inherits "2.0.3" uuid@^3.0.1, uuid@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + version "3.3.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" + integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== -v8-compile-cache@2.0.3, v8-compile-cache@^2.0.0: +v8-compile-cache@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== +v8-compile-cache@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" + integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== + validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -9081,7 +9032,7 @@ w3c-xmlserializer@^1.1.2: webidl-conversions "^4.0.2" xml-name-validator "^3.0.0" -watchpack@^1.5.0: +watchpack@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== @@ -9103,9 +9054,9 @@ webidl-conversions@^4.0.2: integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== webpack-cli@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.6.tgz#2c8c399a2642133f8d736a359007a052e060032c" - integrity sha512-0vEa83M7kJtxK/jUhlpZ27WHIOndz5mghWL2O53kiDoA9DIxSKnfqB92LoqEn77cT4f3H2cZm1BMEat/6AZz3A== + version "3.3.9" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.9.tgz#79c27e71f94b7fe324d594ab64a8e396b9daa91a" + integrity sha512-xwnSxWl8nZtBl/AFJCOn9pG7s5CYUYdZxmmukv+fAHLcBIHM36dImfpQg3WfShZXeArkWlf6QRw24Klcsv8a5A== dependencies: chalk "2.4.2" cross-spawn "6.0.5" @@ -9119,42 +9070,42 @@ webpack-cli@^3.3.6: v8-compile-cache "2.0.3" yargs "13.2.4" -webpack-sources@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" - integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA== +webpack-sources@^1.4.0, webpack-sources@^1.4.1: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== dependencies: source-list-map "^2.0.0" source-map "~0.6.1" webpack@^4.35.3: - version "4.38.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.38.0.tgz#6d77108404b08883c78f4e7e45a43c4e5c47c931" - integrity sha512-lbuFsVOq8PZY+1Ytz/mYOvYOo+d4IJ31hHk/7iyoeWtwN33V+5HYotSH+UIb9tq914ey0Hot7z6HugD+je3sWw== + version "4.41.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.1.tgz#5388dd3047d680d5d382a84249fd4750e87372fd" + integrity sha512-ak7u4tUu/U63sCVxA571IuPZO/Q0pZ9cEXKg+R/woxkDzVovq57uB6L2Hlg/pC8LCU+TWpvtcYwsstivQwMJmw== dependencies: "@webassemblyjs/ast" "1.8.5" "@webassemblyjs/helper-module-context" "1.8.5" "@webassemblyjs/wasm-edit" "1.8.5" "@webassemblyjs/wasm-parser" "1.8.5" - acorn "^6.2.0" - ajv "^6.1.0" - ajv-keywords "^3.1.0" - chrome-trace-event "^1.0.0" + acorn "^6.2.1" + ajv "^6.10.2" + ajv-keywords "^3.4.1" + chrome-trace-event "^1.0.2" enhanced-resolve "^4.1.0" - eslint-scope "^4.0.0" + eslint-scope "^4.0.3" json-parse-better-errors "^1.0.2" - loader-runner "^2.3.0" - loader-utils "^1.1.0" - memory-fs "~0.4.1" - micromatch "^3.1.8" - mkdirp "~0.5.0" - neo-async "^2.5.0" - node-libs-browser "^2.0.0" + loader-runner "^2.4.0" + loader-utils "^1.2.3" + memory-fs "^0.4.1" + micromatch "^3.1.10" + mkdirp "^0.5.1" + neo-async "^2.6.1" + node-libs-browser "^2.2.1" schema-utils "^1.0.0" - tapable "^1.1.0" - terser-webpack-plugin "^1.1.0" - watchpack "^1.5.0" - webpack-sources "^1.3.0" + tapable "^1.1.3" + terser-webpack-plugin "^1.4.1" + watchpack "^1.6.0" + webpack-sources "^1.4.1" whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: version "1.0.5" @@ -9290,12 +9241,12 @@ ws@^5.1.1: dependencies: async-limiter "~1.0.0" -ws@^7.0.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.1.1.tgz#f9942dc868b6dffb72c14fd8f2ba05f77a4d5983" - integrity sha512-o41D/WmDeca0BqYhsr3nJzQyg9NF5X8l/UdnFNux9cS3lwB+swm8qGWX5rn+aD6xfBU3rGmtHij7g7x6LxFU3A== +ws@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== dependencies: - async-limiter "^1.0.0" + async-limiter "~1.0.0" xml-name-validator@^3.0.0: version "3.0.0" @@ -9303,9 +9254,9 @@ xml-name-validator@^3.0.0: integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== xmlchars@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.1.1.tgz#ef1a81c05bff629c2280007f12daca21bd6f6c93" - integrity sha512-7hew1RPJ1iIuje/Y01bGD/mXokXxegAgVS+e+E0wSi2ILHQkYAH1+JXARwTjZSM4Z4Z+c73aKspEcqj+zPPL/w== + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" @@ -9323,14 +9274,14 @@ yallist@^2.1.2: integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yargs-parser@13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.0.0.tgz#3fc44f3e76a8bdb1cc3602e860108602e5ccde8b" - integrity sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw== +yargs-parser@13.1.1, yargs-parser@^13.0.0, yargs-parser@^13.1.0, yargs-parser@^13.1.1: + version "13.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" + integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" @@ -9343,29 +9294,21 @@ yargs-parser@^11.1.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^13.0.0, yargs-parser@^13.1.0, yargs-parser@^13.1.1: - version "13.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" - integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-unparser@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.5.0.tgz#f2bb2a7e83cbc87bb95c8e572828a06c9add6e0d" - integrity sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw== +yargs-unparser@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" + integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== dependencies: flat "^4.1.0" - lodash "^4.17.11" - yargs "^12.0.5" + lodash "^4.17.15" + yargs "^13.3.0" -yargs@13.2.2: - version "13.2.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.2.tgz#0c101f580ae95cea7f39d927e7770e3fdc97f993" - integrity sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA== +yargs@13.2.4: + version "13.2.4" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" + integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== dependencies: - cliui "^4.0.0" + cliui "^5.0.0" find-up "^3.0.0" get-caller-file "^2.0.1" os-locale "^3.1.0" @@ -9375,26 +9318,25 @@ yargs@13.2.2: string-width "^3.0.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^13.0.0" + yargs-parser "^13.1.0" -yargs@13.2.4: - version "13.2.4" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" - integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== +yargs@13.3.0, yargs@^13.2.2, yargs@^13.3.0: + version "13.3.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" + integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA== dependencies: cliui "^5.0.0" find-up "^3.0.0" get-caller-file "^2.0.1" - os-locale "^3.1.0" require-directory "^2.1.1" require-main-filename "^2.0.0" set-blocking "^2.0.0" string-width "^3.0.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^13.1.0" + yargs-parser "^13.1.1" -yargs@^12.0.1, yargs@^12.0.5: +yargs@^12.0.1: version "12.0.5" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== @@ -9411,19 +9353,3 @@ yargs@^12.0.1, yargs@^12.0.5: which-module "^2.0.0" y18n "^3.2.1 || ^4.0.0" yargs-parser "^11.1.1" - -yargs@^13.2.2: - version "13.3.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" - integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.1"

This is a test.