зеркало из https://github.com/microsoft/tiny-calc.git
Types: Separate shape from data
This commit is contained in:
Родитель
446c8c6956
Коммит
a538266223
|
@ -1,3 +1,8 @@
|
||||||
|
/*!
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
import { IProducer, IReader, IConsumer } from "@tiny-calc/types";
|
import { IProducer, IReader, IConsumer } from "@tiny-calc/types";
|
||||||
import { ConsumerSet, addConsumer, removeConsumer, forEachConsumer } from "../consumerset";
|
import { ConsumerSet, addConsumer, removeConsumer, forEachConsumer } from "../consumerset";
|
||||||
|
|
||||||
|
@ -6,12 +11,12 @@ export abstract class Producer<TMap> implements IProducer<TMap>, IReader<TMap> {
|
||||||
|
|
||||||
//#region IProducer
|
//#region IProducer
|
||||||
|
|
||||||
open(consumer: IConsumer<TMap>): IReader<TMap> {
|
public open(consumer: IConsumer<TMap>): IReader<TMap> {
|
||||||
this.consumers = addConsumer(this.consumers, consumer);
|
this.consumers = addConsumer(this.consumers, consumer);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
close(consumer: IConsumer<TMap>): void {
|
public close(consumer: IConsumer<TMap>): void {
|
||||||
this.consumers = removeConsumer(this.consumers, consumer);
|
this.consumers = removeConsumer(this.consumers, consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,15 +24,15 @@ export abstract class Producer<TMap> implements IProducer<TMap>, IReader<TMap> {
|
||||||
|
|
||||||
//#region IReader
|
//#region IReader
|
||||||
|
|
||||||
public abstract get<K extends keyof TMap>(property: K): TMap[K];
|
public abstract get<K extends keyof TMap>(key: K): TMap[K];
|
||||||
|
|
||||||
public get producer() { return this; }
|
public get producer():IProducer<TMap> { return this; }
|
||||||
|
|
||||||
//#endregion IReader
|
//#endregion IReader
|
||||||
|
|
||||||
protected invalidateValue<K extends keyof TMap>(property: K): void {
|
protected invalidateValue<K extends keyof TMap>(key: K): void {
|
||||||
forEachConsumer(this.consumers, (consumer) => {
|
forEachConsumer(this.consumers, (consumer) => {
|
||||||
consumer.valueChanged(property, this);
|
consumer.keyChanged(key, this);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,14 @@
|
||||||
import "mocha";
|
import "mocha";
|
||||||
import { strict as assert } from "assert";
|
import { strict as assert } from "assert";
|
||||||
import { Random } from "best-random";
|
import { Random } from "best-random";
|
||||||
import { IMatrixConsumer, IMatrixReader, IMatrixProducer, IVectorWriter, IMatrixWriter } from "@tiny-calc/types";
|
import {
|
||||||
|
IMatrixConsumer,
|
||||||
|
IMatrixReader,
|
||||||
|
IMatrixProducer,
|
||||||
|
IMatrixWriter,
|
||||||
|
IVectorShapeWriter,
|
||||||
|
IVectorWriter,
|
||||||
|
} from "@tiny-calc/types";
|
||||||
import { DenseVector, RowMajorMatrix } from "../src";
|
import { DenseVector, RowMajorMatrix } from "../src";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -95,8 +102,8 @@ export class TestMatrix<T = any, TRow = never, TCol = never> implements IMatrixC
|
||||||
|
|
||||||
public constructor (
|
public constructor (
|
||||||
producer: IMatrixProducer<T>,
|
producer: IMatrixProducer<T>,
|
||||||
private readonly rowWriter: IVectorWriter<TRow>,
|
private readonly rowWriter: IVectorWriter<TRow> & IVectorShapeWriter,
|
||||||
private readonly colWriter: IVectorWriter<TCol>,
|
private readonly colWriter: IVectorWriter<TCol> & IVectorShapeWriter,
|
||||||
private readonly cellWriter: IMatrixWriter<T>
|
private readonly cellWriter: IMatrixWriter<T>
|
||||||
) {
|
) {
|
||||||
this.reader = producer.openMatrix(this);
|
this.reader = producer.openMatrix(this);
|
||||||
|
@ -109,7 +116,7 @@ export class TestMatrix<T = any, TRow = never, TCol = never> implements IMatrixC
|
||||||
|
|
||||||
public rowsChanged(rowStart: number, removedCount: number, insertedCount: number): void {
|
public rowsChanged(rowStart: number, removedCount: number, insertedCount: number): void {
|
||||||
const rowEnd = rowStart + removedCount;
|
const rowEnd = rowStart + removedCount;
|
||||||
|
|
||||||
assert(0 <= rowStart && rowStart <= rowEnd && rowEnd <= this.consumed.rowCount);
|
assert(0 <= rowStart && rowStart <= rowEnd && rowEnd <= this.consumed.rowCount);
|
||||||
|
|
||||||
if (removedCount > 0) { this.consumed.removeRows(rowStart, removedCount); }
|
if (removedCount > 0) { this.consumed.removeRows(rowStart, removedCount); }
|
||||||
|
@ -154,7 +161,7 @@ export class TestMatrix<T = any, TRow = never, TCol = never> implements IMatrixC
|
||||||
|
|
||||||
const actual = this.reader.getCell(row, col);
|
const actual = this.reader.getCell(row, col);
|
||||||
const expected = this.expected.getCell(row, col);
|
const expected = this.expected.getCell(row, col);
|
||||||
|
|
||||||
assert.equal(actual, expected);
|
assert.equal(actual, expected);
|
||||||
assert.equal(this.consumed.getCell(row, col), expected);
|
assert.equal(this.consumed.getCell(row, col), expected);
|
||||||
|
|
||||||
|
@ -173,7 +180,7 @@ export class TestMatrix<T = any, TRow = never, TCol = never> implements IMatrixC
|
||||||
|
|
||||||
this.expected.setCell(row, col, value);
|
this.expected.setCell(row, col, value);
|
||||||
this.cellWriter.setCell(row, col, value);
|
this.cellWriter.setCell(row, col, value);
|
||||||
|
|
||||||
assert.equal(this.getCell(row, col), value,
|
assert.equal(this.getCell(row, col), value,
|
||||||
`Writer.setCell(${row},${col}) must update matrix value.`);
|
`Writer.setCell(${row},${col}) must update matrix value.`);
|
||||||
}
|
}
|
||||||
|
@ -239,7 +246,7 @@ export class TestMatrix<T = any, TRow = never, TCol = never> implements IMatrixC
|
||||||
}
|
}
|
||||||
|
|
||||||
public extract(): ReadonlyArray<ReadonlyArray<T>> {
|
public extract(): ReadonlyArray<ReadonlyArray<T>> {
|
||||||
const m: T[][] = [];
|
const m: T[][] = [];
|
||||||
for (let r = 0; r < this.rowCount; r++) {
|
for (let r = 0; r < this.rowCount; r++) {
|
||||||
const row: T[] = [];
|
const row: T[] = [];
|
||||||
m.push(row);
|
m.push(row);
|
||||||
|
@ -265,7 +272,7 @@ export class TestMatrix<T = any, TRow = never, TCol = never> implements IMatrixC
|
||||||
|
|
||||||
public expectSize(rowCount: number, colCount: number): void {
|
public expectSize(rowCount: number, colCount: number): void {
|
||||||
this.check();
|
this.check();
|
||||||
|
|
||||||
assert.equal(this.rowCount, rowCount);
|
assert.equal(this.rowCount, rowCount);
|
||||||
assert.equal(this.colCount, colCount);
|
assert.equal(this.colCount, colCount);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import "mocha";
|
import "mocha";
|
||||||
import { strict as assert } from "assert";
|
import { strict as assert } from "assert";
|
||||||
import { Random } from "best-random";
|
import { Random } from "best-random";
|
||||||
import { IVectorConsumer, IVectorReader, IVectorProducer, IVectorWriter } from "@tiny-calc/types";
|
import { IVectorConsumer, IVectorReader, IVectorProducer, IVectorWriter, IVectorShapeWriter } from "@tiny-calc/types";
|
||||||
import { DenseVector } from "../src";
|
import { DenseVector } from "../src";
|
||||||
|
|
||||||
export class TestVector<T> implements IVectorConsumer<T> {
|
export class TestVector<T> implements IVectorConsumer<T> {
|
||||||
|
@ -14,7 +14,7 @@ export class TestVector<T> implements IVectorConsumer<T> {
|
||||||
private readonly consumed: T[] = [];
|
private readonly consumed: T[] = [];
|
||||||
private readonly expected: T[] = [];
|
private readonly expected: T[] = [];
|
||||||
|
|
||||||
public constructor(producer: IVectorProducer<T>, private readonly writer: IVectorWriter<T>) {
|
public constructor(producer: IVectorProducer<T>, private readonly writer: IVectorWriter<T> & IVectorShapeWriter) {
|
||||||
this.actual = producer.openVector(this);
|
this.actual = producer.openVector(this);
|
||||||
|
|
||||||
for (let i = 0; i < this.actual.length; i++) {
|
for (let i = 0; i < this.actual.length; i++) {
|
||||||
|
@ -41,11 +41,11 @@ export class TestVector<T> implements IVectorConsumer<T> {
|
||||||
|
|
||||||
public itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorProducer<T>): void {
|
public itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorProducer<T>): void {
|
||||||
const inserted = [];
|
const inserted = [];
|
||||||
|
|
||||||
for (let i = start; insertedCount > 0; i++, insertedCount--) {
|
for (let i = start; insertedCount > 0; i++, insertedCount--) {
|
||||||
inserted.push(this.actual.getItem(i));
|
inserted.push(this.actual.getItem(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.consumed.splice(start, removedCount, ...inserted);
|
this.consumed.splice(start, removedCount, ...inserted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* Licensed under the MIT License.
|
* Licensed under the MIT License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IConsumer,
|
IConsumer,
|
||||||
IProducer,
|
IProducer,
|
||||||
IVectorConsumer,
|
IVectorConsumer,
|
||||||
|
@ -25,33 +25,33 @@ export class LoggingConsumer<T> implements IConsumer<T>, IVectorConsumer<T>, IMa
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(producer as any)[idSym] = value;
|
(producer as any)[idSym] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getProducerId(producer: AnyProducer): string {
|
private getProducerId(producer: AnyProducer): string {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return (producer as any)[idSym] ?? "Error: Missing call to LoggingConsumer.setProducerId(..)";
|
return (producer as any)[idSym] ?? "Error: Missing call to LoggingConsumer.setProducerId(..)";
|
||||||
}
|
}
|
||||||
|
|
||||||
// #region IConsumer<T>
|
// #region IConsumer<T>
|
||||||
public valueChanged<U extends T, K extends keyof U>(property: K, producer: IProducer<U>): void {
|
public keyChanged<U extends T, K extends keyof U>(property: K, producer: IProducer<U>): void {
|
||||||
this.log.push({ property, producer: this.getProducerId(producer) });
|
this.log.push({ property, producer: this.getProducerId(producer) });
|
||||||
}
|
}
|
||||||
// #endregion IConsumer<T>
|
// #endregion IConsumer<T>
|
||||||
|
|
||||||
// #region IVectorConsumer<T>
|
// #region IVectorConsumer<T>
|
||||||
public itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorProducer<T>): void {
|
public itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorProducer<T>): void {
|
||||||
this.log.push({ start, removedCount, insertedCount, producer: this.getProducerId(producer) });
|
this.log.push({ start, removedCount, insertedCount, producer: this.getProducerId(producer) });
|
||||||
}
|
}
|
||||||
// #endregion IVectorConsumer<T>
|
// #endregion IVectorConsumer<T>
|
||||||
|
|
||||||
// #region IMatrixConsumer<T>
|
// #region IMatrixConsumer<T>
|
||||||
public rowsChanged(rowStart: number, removedCount: number, insertedCount: number, producer: IMatrixProducer<T>): void {
|
public rowsChanged(rowStart: number, removedCount: number, insertedCount: number, producer: IMatrixProducer<T>): void {
|
||||||
this.log.push({ rowStart, removedCount, insertedCount, producer: this.getProducerId(producer) });
|
this.log.push({ rowStart, removedCount, insertedCount, producer: this.getProducerId(producer) });
|
||||||
}
|
}
|
||||||
|
|
||||||
public colsChanged(colStart: number, removedCount: number, insertedCount: number, producer: IMatrixProducer<T>): void {
|
public colsChanged(colStart: number, removedCount: number, insertedCount: number, producer: IMatrixProducer<T>): void {
|
||||||
this.log.push({ colStart, removedCount, insertedCount, producer: this.getProducerId(producer) });
|
this.log.push({ colStart, removedCount, insertedCount, producer: this.getProducerId(producer) });
|
||||||
}
|
}
|
||||||
|
|
||||||
public cellsChanged(rowStart: number, colStart: number, rowCount: number, colCount: number, producer: IMatrixProducer<T>): void {
|
public cellsChanged(rowStart: number, colStart: number, rowCount: number, colCount: number, producer: IMatrixProducer<T>): void {
|
||||||
this.log.push({ rowStart, colStart, rowCount, colCount, producer: this.getProducerId(producer) });
|
this.log.push({ rowStart, colStart, rowCount, colCount, producer: this.getProducerId(producer) });
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ class NullConsumer<T> implements IConsumer<T>, IVectorConsumer<T>, IMatrixConsum
|
||||||
public rowsChanged(): void { }
|
public rowsChanged(): void { }
|
||||||
public colsChanged(): void { }
|
public colsChanged(): void { }
|
||||||
public cellsChanged(): void { }
|
public cellsChanged(): void { }
|
||||||
public valueChanged(): void { }
|
public keyChanged(): void { }
|
||||||
public itemsChanged(): void { }
|
public itemsChanged(): void { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,5 @@ const listFormula = new ListFormula(context, "IF(Time.Now = Time.Now, Math.Max(T
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
listFormula.valueChanged();
|
listFormula.keyChanged();
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ export class Context implements Producer {
|
||||||
|
|
||||||
open() {
|
open() {
|
||||||
const producer: Producer = this;
|
const producer: Producer = this;
|
||||||
return {
|
return {
|
||||||
get: (key: string) => this.fields[key],
|
get: (key: string) => this.fields[key],
|
||||||
producer
|
producer
|
||||||
};
|
};
|
||||||
|
@ -47,7 +47,7 @@ export class TimeProducer implements Producer {
|
||||||
open() {
|
open() {
|
||||||
const time = Date.now();
|
const time = Date.now();
|
||||||
const producer: Producer = this;
|
const producer: Producer = this;
|
||||||
return {
|
return {
|
||||||
get: () => time,
|
get: () => time,
|
||||||
producer
|
producer
|
||||||
};
|
};
|
||||||
|
@ -114,7 +114,7 @@ export class ListFormula implements FormulaHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
valueChanged() {
|
keyChanged() {
|
||||||
console.time("recalc");
|
console.time("recalc");
|
||||||
const scope = createCalcValue(this.scope);
|
const scope = createCalcValue(this.scope);
|
||||||
const values = [];
|
const values = [];
|
||||||
|
|
|
@ -3,9 +3,36 @@
|
||||||
* Licensed under the MIT License.
|
* Licensed under the MIT License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { IConsumer, IProducer, IReader, IWriter } from './record';
|
export {
|
||||||
export { IVectorConsumer, IVectorProducer, IVectorReader, IVectorWriter } from './vector';
|
IConsumer,
|
||||||
export { IMatrixConsumer, IMatrixProducer, IMatrixReader, IMatrixWriter } from './matrix';
|
IProducer,
|
||||||
|
IReader,
|
||||||
|
IShapeConsumer,
|
||||||
|
IShapeProducer,
|
||||||
|
IShapeReader,
|
||||||
|
IShapeWriter,
|
||||||
|
IWriter
|
||||||
|
} from './record';
|
||||||
|
export {
|
||||||
|
IVectorConsumer,
|
||||||
|
IVectorProducer,
|
||||||
|
IVectorReader,
|
||||||
|
IVectorShapeConsumer,
|
||||||
|
IVectorShapeProducer,
|
||||||
|
IVectorShapeReader,
|
||||||
|
IVectorShapeWriter,
|
||||||
|
IVectorWriter
|
||||||
|
} from './vector';
|
||||||
|
export {
|
||||||
|
IMatrixConsumer,
|
||||||
|
IMatrixProducer,
|
||||||
|
IMatrixReader,
|
||||||
|
IMatrixShapeConsumer,
|
||||||
|
IMatrixShapeProducer,
|
||||||
|
IMatrixShapeReader,
|
||||||
|
IMatrixShapeWriter,
|
||||||
|
IMatrixWriter
|
||||||
|
} from './matrix';
|
||||||
export {
|
export {
|
||||||
ITreeShapeConsumer,
|
ITreeShapeConsumer,
|
||||||
ITreeShapeProducer,
|
ITreeShapeProducer,
|
||||||
|
|
|
@ -4,27 +4,80 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** An observable 2D numerically indexed collection. */
|
/** An observable 2D numerically indexed collection. */
|
||||||
export interface IMatrixProducer<T> {
|
export interface IMatrixShapeProducer {
|
||||||
/**
|
/**
|
||||||
* Acquire a reader for this matrix's values and implicitly subscribe the consumer
|
* Acquire a reader for this matrix's values and implicitly subscribe the consumer
|
||||||
* to value change notifications.
|
* to value change notifications.
|
||||||
*
|
*
|
||||||
|
* @param consumer - The consumer to be notified of matrix changes.
|
||||||
|
*/
|
||||||
|
openMatrix(consumer: IMatrixShapeConsumer): IMatrixShapeReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe the consumer from this matrix's change notifications.
|
||||||
|
*
|
||||||
|
* @param consumer - The consumer to unregister from the matrix.
|
||||||
|
*/
|
||||||
|
closeMatrix(consumer: IMatrixShapeConsumer): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A consumer of change notifications for a matrix. */
|
||||||
|
export interface IMatrixShapeConsumer {
|
||||||
|
/** Notification that rows have been inserted, removed, and/or replaced in the given matrix. */
|
||||||
|
rowsChanged(rowStart: number, removedCount: number, insertedCount: number, producer: IMatrixShapeProducer): void;
|
||||||
|
|
||||||
|
/** Notification that cols have been inserted, removed, and/or replaced in the given matrix. */
|
||||||
|
colsChanged(colStart: number, removedCount: number, insertedCount: number, producer: IMatrixShapeProducer): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Capability to read cells in a matrix. */
|
||||||
|
export interface IMatrixShapeReader {
|
||||||
|
readonly rowCount: number;
|
||||||
|
readonly colCount: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to the underlying producer that provides values for this reader,
|
||||||
|
* or undefined if the producer is immutable.
|
||||||
|
*/
|
||||||
|
readonly matrixProducer?: IMatrixShapeProducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Capability to write cells in a matrix. */
|
||||||
|
export interface IMatrixShapeWriter {
|
||||||
|
spliceRows(rowStart: number, deleteCount: number, insertCount: number): void;
|
||||||
|
spliceCols(colStart: number, deleteCount: number, insertCount: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An observable 2D numerically indexed collection. */
|
||||||
|
export interface IMatrixProducer<T> extends IMatrixShapeProducer {
|
||||||
|
/**
|
||||||
|
* Acquire a reader for this matrix's values and implicitly subscribe the consumer
|
||||||
|
* to value change notifications.
|
||||||
|
*
|
||||||
* @param consumer - The consumer to be notified of matrix changes.
|
* @param consumer - The consumer to be notified of matrix changes.
|
||||||
*/
|
*/
|
||||||
openMatrix(consumer: IMatrixConsumer<T>): IMatrixReader<T>;
|
openMatrix(consumer: IMatrixConsumer<T>): IMatrixReader<T>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsubscribe the consumer from this matrix's change notifications.
|
* Unsubscribe the consumer from this matrix's change notifications.
|
||||||
*
|
*
|
||||||
* @param consumer - The consumer to unregister from the matrix.
|
* @param consumer - The consumer to unregister from the matrix.
|
||||||
*/
|
*/
|
||||||
closeMatrix(consumer: IMatrixConsumer<T>): void;
|
closeMatrix(consumer: IMatrixConsumer<T>): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A consumer of change notifications for a matrix. */
|
||||||
|
export interface IMatrixConsumer<T> extends IMatrixShapeConsumer {
|
||||||
|
/**
|
||||||
|
* Notification that a range of cells have been replaced in the given matrix. If the source
|
||||||
|
* matrix has the new cell values already in an array, it may optionally pass these to consumers
|
||||||
|
* as an optimization.
|
||||||
|
*/
|
||||||
|
cellsChanged(rowStart: number, colStart: number, rowCount: number, colCount: number, producer: IMatrixProducer<T>): void;
|
||||||
|
}
|
||||||
|
|
||||||
/** Capability to read cells in a matrix. */
|
/** Capability to read cells in a matrix. */
|
||||||
export interface IMatrixReader<T> {
|
export interface IMatrixReader<T> extends IMatrixShapeReader {
|
||||||
readonly rowCount: number;
|
|
||||||
readonly colCount: number;
|
|
||||||
getCell(row: number, col: number): T;
|
getCell(row: number, col: number): T;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,22 +92,6 @@ export interface IMatrixWriter<T> {
|
||||||
setCell(row: number, col: number, value: T): void;
|
setCell(row: number, col: number, value: T): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A consumer of change notifications for a matrix. */
|
|
||||||
export interface IMatrixConsumer<T> {
|
|
||||||
/** Notification that rows have been inserted, removed, and/or replaced in the given matrix. */
|
|
||||||
rowsChanged(rowStart: number, removedCount: number, insertedCount: number, producer: IMatrixProducer<T>): void;
|
|
||||||
|
|
||||||
/** Notification that cols have been inserted, removed, and/or replaced in the given matrix. */
|
|
||||||
colsChanged(colStart: number, removedCount: number, insertedCount: number, producer: IMatrixProducer<T>): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notification that a range of cells have been replaced in the given matrix. If the source
|
|
||||||
* matrix has the new cell values already in an array, it may optionally pass these to consumers
|
|
||||||
* as an optimization.
|
|
||||||
*/
|
|
||||||
cellsChanged(rowStart: number, colStart: number, rowCount: number, colCount: number, producer: IMatrixProducer<T>): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MatrixIteratorSpec {
|
export interface MatrixIteratorSpec {
|
||||||
/**
|
/**
|
||||||
* Iterates over empty cells.
|
* Iterates over empty cells.
|
||||||
|
|
|
@ -3,45 +3,85 @@
|
||||||
* Licensed under the MIT License.
|
* Licensed under the MIT License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/** An observable key/value collection, such as a Record or Map. */
|
||||||
|
export interface IShapeProducer<TKey> {
|
||||||
|
/**
|
||||||
|
* Acquire a reader for this producer's values and implicitly subscribe the consumer
|
||||||
|
* to value change notifications.
|
||||||
|
*
|
||||||
|
* @param consumer - The consumer to be notified of value changes.
|
||||||
|
*/
|
||||||
|
open(consumer: IShapeConsumer<TKey>): IShapeReader<TKey>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe the given 'consumer' from this producer's change notifications.
|
||||||
|
*
|
||||||
|
* @param consumer - The consumer to unregister from the producer.
|
||||||
|
*/
|
||||||
|
close(consumer: IShapeConsumer<TKey>): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A consumer of change notifications for a key/value collection, such as a Record or Map. */
|
||||||
|
export interface IShapeConsumer<TKey> {
|
||||||
|
keyChanged(key: TKey, producer: IShapeProducer<TKey>): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Capability to read values of a key/value collection. */
|
||||||
|
export interface IShapeReader<TKey> {
|
||||||
|
keys(): IterableIterator<TKey>;
|
||||||
|
|
||||||
|
has(key: TKey): boolean;
|
||||||
|
|
||||||
|
readonly size: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to the underlying producer that provides the shape for this reader,
|
||||||
|
* or undefined if the producer is immutable.
|
||||||
|
*/
|
||||||
|
readonly producer?: IShapeProducer<TKey>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IShapeWriter<TKey> {
|
||||||
|
delete(key: TKey): void;
|
||||||
|
clear(): void;
|
||||||
|
}
|
||||||
|
|
||||||
/** An observable key/value collection, such as a Record or Map. */
|
/** An observable key/value collection, such as a Record or Map. */
|
||||||
export interface IProducer<T> {
|
export interface IProducer<T> {
|
||||||
/**
|
/**
|
||||||
* Acquire a reader for this producer's values and implicitly subscribe the consumer
|
* Acquire a reader for this producer's values and implicitly subscribe the consumer
|
||||||
* to value change notifications.
|
* to value change notifications.
|
||||||
*
|
*
|
||||||
* @param consumer - The consumer to be notified of value changes.
|
* @param consumer - The consumer to be notified of value changes.
|
||||||
*/
|
*/
|
||||||
open(consumer: IConsumer<T>): IReader<T>;
|
open(consumer: IConsumer<T>): IReader<T> | (IReader<T> & IShapeReader<T>);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsubscribe the given 'consumer' from this producer's change notifications.
|
* Unsubscribe the given 'consumer' from this producer's change notifications.
|
||||||
*
|
*
|
||||||
* @param consumer - The consumer to unregister from the producer.
|
* @param consumer - The consumer to unregister from the producer.
|
||||||
*/
|
*/
|
||||||
close(consumer: IConsumer<T>): void;
|
close(consumer: IConsumer<T>): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A consumer of change notifications for a key/value collection, such as a Record or Map. */
|
||||||
|
export interface IConsumer<T> {
|
||||||
|
keyChanged<K extends keyof T>(key: K, producer: IProducer<T>): void;
|
||||||
|
}
|
||||||
|
|
||||||
/** Capability to read values of a key/value collection. */
|
/** Capability to read values of a key/value collection. */
|
||||||
export interface IReader<T> {
|
export interface IReader<T> {
|
||||||
/** Return the value associated with `property`. */
|
/** Return the value associated with `key`. */
|
||||||
get<K extends keyof T>(property: K): T[K];
|
get<K extends keyof T>(key: K): T[K];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reference to the underlying producer that provides values for this reader,
|
* A reference to the underlying producer that provides values for this reader,
|
||||||
* or undefined if the producer is immutable.
|
* or undefined if the producer is immutable.
|
||||||
*/
|
*/
|
||||||
readonly producer?: IProducer<T>;
|
readonly producer?: IProducer<T> | (IProducer<T> & IShapeProducer<keyof T>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Capability to set values of a key/value collection. */
|
/** Capability to set values of a key/value collection. */
|
||||||
export interface IWriter<T> {
|
export interface IWriter<T> {
|
||||||
set<K extends keyof T>(property: K, value: T[K]): void;
|
set<K extends keyof T>(key: K, value: T[K]): void;
|
||||||
}
|
|
||||||
|
|
||||||
/** A consumer of change notifications for a key/value collection, such as a Record or Map. */
|
|
||||||
export interface IConsumer<T> {
|
|
||||||
/**
|
|
||||||
* Invoked whenever the data this object is bound to is changed.
|
|
||||||
*/
|
|
||||||
valueChanged<U extends T, K extends keyof U>(property: K, producer: IProducer<U>): void;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,26 +4,64 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** An observable 1D numerically indexed collection, such as an Array. */
|
/** An observable 1D numerically indexed collection, such as an Array. */
|
||||||
export interface IVectorProducer<T> {
|
export interface IVectorShapeProducer {
|
||||||
/**
|
/**
|
||||||
* Acquire a reader for this vector's values and implicitly subscribe the consumer
|
* Acquire a reader for this vector's values and implicitly subscribe the consumer
|
||||||
* to value change notifications.
|
* to value change notifications.
|
||||||
*
|
*
|
||||||
|
* @param consumer - The consumer to be notified of vector changes.
|
||||||
|
*/
|
||||||
|
openVector(consumer: IVectorShapeConsumer): IVectorShapeReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe the given 'consumer' from this vector's change notifications.
|
||||||
|
*
|
||||||
|
* @param consumer - The consumer to unregister from the vector.
|
||||||
|
*/
|
||||||
|
closeVector(consumer: IVectorShapeConsumer): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Capability to read items in a vector. */
|
||||||
|
export interface IVectorShapeReader {
|
||||||
|
readonly length: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to the underlying producer that provides values for this reader,
|
||||||
|
* or undefined if the producer is immutable.
|
||||||
|
*/
|
||||||
|
readonly vectorProducer?: IVectorShapeProducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Capability to insert, replace, and remove items in a vector. */
|
||||||
|
export interface IVectorShapeWriter {
|
||||||
|
splice(start: number, deleteCount: number, insertCount: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVectorShapeConsumer {
|
||||||
|
/** Notification that a range of items have been inserted, removed, and/or replaced in the given vector. */
|
||||||
|
itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorShapeProducer): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An observable 1D numerically indexed collection, such as an Array. */
|
||||||
|
export interface IVectorProducer<T> extends IVectorShapeProducer {
|
||||||
|
/**
|
||||||
|
* Acquire a reader for this vector's values and implicitly subscribe the consumer
|
||||||
|
* to value change notifications.
|
||||||
|
*
|
||||||
* @param consumer - The consumer to be notified of vector changes.
|
* @param consumer - The consumer to be notified of vector changes.
|
||||||
*/
|
*/
|
||||||
openVector(consumer: IVectorConsumer<T>): IVectorReader<T>;
|
openVector(consumer: IVectorConsumer<T>): IVectorReader<T>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsubscribe the given 'consumer' from this vector's change notifications.
|
* Unsubscribe the given 'consumer' from this vector's change notifications.
|
||||||
*
|
*
|
||||||
* @param consumer - The consumer to unregister from the vector.
|
* @param consumer - The consumer to unregister from the vector.
|
||||||
*/
|
*/
|
||||||
closeVector(consumer: IVectorConsumer<T>): void;
|
closeVector(consumer: IVectorConsumer<T>): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Capability to read items in a vector. */
|
/** Capability to read items in a vector. */
|
||||||
export interface IVectorReader<T> {
|
export interface IVectorReader<T> extends IVectorShapeReader {
|
||||||
readonly length: number;
|
|
||||||
getItem(index: number): T;
|
getItem(index: number): T;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,12 +73,11 @@ export interface IVectorReader<T> {
|
||||||
|
|
||||||
/** Capability to insert, replace, and remove items in a vector. */
|
/** Capability to insert, replace, and remove items in a vector. */
|
||||||
export interface IVectorWriter<T> {
|
export interface IVectorWriter<T> {
|
||||||
splice(start: number, deleteCount: number, insertCount: number): void;
|
|
||||||
setItem(index: number, item: T): void;
|
setItem(index: number, item: T): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A consumer of change notifications for a vector. */
|
/** A consumer of change notifications for a vector. */
|
||||||
export interface IVectorConsumer<T> {
|
export interface IVectorConsumer<T> extends IVectorShapeConsumer {
|
||||||
/** Notification that a range of items have been inserted, removed, and/or replaced in the given vector. */
|
/** Notification that a range of items have been inserted, removed, and/or replaced in the given vector. */
|
||||||
itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorProducer<T>): void;
|
itemsChanged(start: number, removedCount: number, insertedCount: number, producer: IVectorProducer<T>): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,32 +5,63 @@
|
||||||
|
|
||||||
import "mocha";
|
import "mocha";
|
||||||
import { strict as assert } from "assert";
|
import { strict as assert } from "assert";
|
||||||
import { IReader, IWriter } from "../src";
|
import { IReader, IWriter, IShapeReader, IShapeWriter } from "../src";
|
||||||
|
|
||||||
describe("Record", () => {
|
describe("Record", () => {
|
||||||
describe("must be compatible with ES6 map", () => {
|
describe("must be compatible with ES6 map", () => {
|
||||||
let map: Map<string, number>;
|
let map: Map<string, number>;
|
||||||
|
let shapeReader: IShapeReader<string>;
|
||||||
|
let shapeWriter: IShapeWriter<string>;
|
||||||
let reader: IReader<Record<string, number | undefined>>;
|
let reader: IReader<Record<string, number | undefined>>;
|
||||||
let writer: IWriter<Record<string, number>>;
|
let writer: IWriter<Record<string, number>>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
map = new Map<string, number>();
|
map = new Map<string, number>();
|
||||||
|
shapeReader = map;
|
||||||
|
shapeWriter = map;
|
||||||
reader = map;
|
reader = map;
|
||||||
writer = map;
|
writer = map;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ES6 map must satisfy IReader", () => {
|
it("ES6 map must satisfy IReader", () => {
|
||||||
map.set("0", 0);
|
map.set("0", 0);
|
||||||
assert.equal(reader.get("0"), 0);
|
assert.equal(reader.get("0"), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ES6 map must satisfy IWriter", () => {
|
it("ES6 map must satisfy IWriter", () => {
|
||||||
writer.set("0", 0);
|
writer.set("0", 0);
|
||||||
assert.equal(map.get("0"), 0);
|
assert.equal(map.get("0"), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("ES6 map must satisfy IShapeReader", () => {
|
||||||
|
assert.deepEqual([...shapeReader.keys()], []);
|
||||||
|
|
||||||
|
const keys = ["a", "b", "c"];
|
||||||
|
for (const key of keys) {
|
||||||
|
assert.equal(shapeReader.has(key), false);
|
||||||
|
map.set(key, key.charCodeAt(0));
|
||||||
|
assert.equal(shapeReader.has(key), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual([...shapeReader.keys()], keys);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ES6 map must satisfy IShapeWriter", () => {
|
||||||
|
const keys = ["a", "b", "c"];
|
||||||
|
for (const key of keys) {
|
||||||
|
assert.equal(shapeReader.has(key), false);
|
||||||
|
assert.equal(shapeWriter.delete(key), false);
|
||||||
|
|
||||||
|
map.set(key, key.charCodeAt(0));
|
||||||
|
|
||||||
|
assert.equal(shapeReader.has(key), true);
|
||||||
|
assert.equal(shapeWriter.delete(key), true);
|
||||||
|
assert.equal(shapeReader.has(key), false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("Must support key constraints", () => {
|
it("Must support key constraints", () => {
|
||||||
const map = new Map([
|
const map = new Map<"0" | "1", number>([
|
||||||
["0", 0],
|
["0", 0],
|
||||||
["1", 1],
|
["1", 1],
|
||||||
]);
|
]);
|
||||||
|
@ -39,7 +70,7 @@ describe("Record", () => {
|
||||||
"0": number | undefined,
|
"0": number | undefined,
|
||||||
"1": number | undefined,
|
"1": number | undefined,
|
||||||
}> = map;
|
}> = map;
|
||||||
|
|
||||||
assert.equal(reader.get("0"), 0);
|
assert.equal(reader.get("0"), 0);
|
||||||
assert.equal(reader.get("1"), 1);
|
assert.equal(reader.get("1"), 1);
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче