UNPKG

ayvajs

Version:

A lightweight, behavior-based JavaScript API for controlling Open Source Multi Axis Stroker Robots.

184 lines (150 loc) 5.01 kB
import Callable from '../util/callable.js'; import MoveBuilder from '../util/move-builder.js'; import Ayva from '../ayva.js'; // eslint-disable-line import/no-cycle /** * Base class for Generator Behaviors. * * For full details on how to use this class, see the {@tutorial behavior-api} tutorial. */ class GeneratorBehavior extends Callable { #generator; #done = false; #ayva = null; #unboundStart; #unboundGenerate; #unboundIterated; get ayva () { return this.#ayva; } constructor (generate) { super(); if (generate) { if (generate.constructor.name !== 'GeneratorFunction') { throw Error(`Not a generator function: ${generate}`); } this.generate = generate; } } /** * Generate any actions that setup this behavior. Typically this is moving the device into * some starting position. Subclasses can implement this method, although it is optional. * * For full details on how to implement this method, see the {@tutorial behavior-api} tutorial. * * @param {Ayva} [ayva] - instance of Ayva to generate actions for. Defaults to the Ayva instance bound to this behavior (if it exists). *//* c8 ignore start */ * start (ayva) { // eslint-disable-line require-yield, no-unused-vars // Optional }/* c8 ignore stop */ /** * Generate the actions that make up this behavior. Subclasses must implement this method. * * For full details on how to implement this method, see the {@tutorial behavior-api} tutorial. * * @param {Ayva} [ayva] - instance of Ayva to generate actions for. Defaults to the Ayva instance bound to this behavior (if it exists). */ * generate (ayva) { // eslint-disable-line require-yield, no-unused-vars throw new Error('generate() not implemented.'); } /** * Repeats the actions that make up this behavior the specified number of iterations. * * @param {Ayva} [ayva] - instance of Ayva to generate actions for. Defaults to the Ayva instance bound to this behavior (if it exists). * @param {Number} count - the number of iterations */ * iterated (ayva, count = 1) { for (let i = 0; i < count; i++) { yield* this.generate(ayva); } } /** * Perform one step of this behavior. * * @param {Ayva} [ayva] - instance of Ayva to use. Defaults to the Ayva instance bound to this behavior (if it exists). * @returns Promise that resolves when the step is complete. */ perform (ayva = this.#ayva) { const value = this.next(ayva); if (value) { if (Array.isArray(value)) { return ayva.move(...value); } else if (Number.isFinite(value) && value >= 0) { return ayva.ready().then(() => ayva.sleep(value)); } else if (value instanceof MoveBuilder) { return value.execute(); } else if (value instanceof Promise) { return value; } return ayva.move(value); } return Promise.resolve(); } /** * Binds this behavior to the specified Ayva instance. * * Not to be confused with Function.prototype.bind(). Although the effect is similar. * This affects start(), generate(), iterated(), and when calling this behavior * using the shorthand callable syntax. * * @param {Ayva} ayva * @returns this behavior */ bind (ayva) { this.unbind(); this.#ayva = ayva; this.#unboundStart = this.start; this.#unboundGenerate = this.generate; this.#unboundIterated = this.iterated; this.start = this.start.bind(this, ayva); this.generate = this.generate.bind(this, ayva); this.iterated = this.iterated.bind(this, ayva); return this; } /** * Unbind this behavior from any Ayva instance. */ unbind () { if (this.#ayva) { this.#ayva = null; this.start = this.#unboundStart; this.generate = this.#unboundGenerate; this.iterated = this.#unboundIterated; } } /** * Generate and return the next action of this behavior. * * @param {Ayva} ayva - defaults to the bound instance (if any) * @returns - the next action. */ next (ayva = this.#ayva) { if (!this.#generator || this.#done) { this.#generator = this.generate(ayva); this.#done = false; } const { value, done } = this.#generator.next(); this.#done = done; return !done ? value : null; } /** * Implementation for Callable. */ __call__ (...args) { const countArg = this.#ayva ? args[0] : args[1]; const ayva = this.#ayva || args[0]; const count = Number.isFinite(countArg) && countArg >= 1 ? countArg : undefined; if (ayva !== undefined) { if (!(ayva instanceof Ayva)) { throw new TypeError(`Invalid Ayva instance: ${ayva}`); } } if (this.#ayva) { return count ? this.iterated(count) : this.generate(); } return count ? this.iterated(ayva, count) : this.generate(ayva); } [Symbol.iterator] () { return this.generate(); } } export default GeneratorBehavior;