From fe572f89d9bc12b1265618545215f3d7ba4f6a59 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Sat, 28 Oct 2017 18:54:41 +0200
Subject: [PATCH] refactoring column part 1

---
 src/mol-base/collections/_spec/column.spec.ts |  34 ++++
 src/mol-base/collections/column.1.ts          | 191 ++++++++++++++++++
 src/mol-base/collections/column.ts            |   2 +-
 .../{ => integer}/_spec/interval.spec.ts      |   2 +-
 .../{ => integer}/_spec/ordered-set.spec.ts   |   4 +-
 .../{ => integer}/_spec/segmentation.spec.ts  |   8 +-
 .../{ => integer}/_spec/sorted-array.spec.ts  |   4 +-
 .../_spec/tuple.spec.ts}                      |   2 +-
 .../collections/integer/segmentation.ts       |   3 +-
 src/mol-data/model.ts                         |  12 +-
 src/mol-data/model/builders/mmcif.ts          |   4 +-
 src/mol-data/structure.ts                     |   1 +
 src/perf-tests/sets.ts                        |   2 +-
 13 files changed, 248 insertions(+), 21 deletions(-)
 create mode 100644 src/mol-base/collections/_spec/column.spec.ts
 create mode 100644 src/mol-base/collections/column.1.ts
 rename src/mol-base/collections/{ => integer}/_spec/interval.spec.ts (98%)
 rename src/mol-base/collections/{ => integer}/_spec/ordered-set.spec.ts (99%)
 rename src/mol-base/collections/{ => integer}/_spec/segmentation.spec.ts (86%)
 rename src/mol-base/collections/{ => integer}/_spec/sorted-array.spec.ts (95%)
 rename src/mol-base/collections/{_spec/int-tuple.spec.ts => integer/_spec/tuple.spec.ts} (92%)

diff --git a/src/mol-base/collections/_spec/column.spec.ts b/src/mol-base/collections/_spec/column.spec.ts
new file mode 100644
index 000000000..5449d7d8e
--- /dev/null
+++ b/src/mol-base/collections/_spec/column.spec.ts
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import Column, { isTypedArray } from '../column.1'
+
+describe('column', () => {
+    const cc = Column.ofConst(10, 2, Column.Type.int);
+    const arr = Column.ofArray({ array: [1, 2, 3, 4], type: Column.Type.int });
+    const arrWindow = Column.window(arr, 1, 3);
+
+    const typed = Column.ofArray({ array: new Int32Array([1, 2, 3, 4]), type: Column.Type.int });
+    const typedWindow = Column.window(typed, 1, 3);
+
+    it('constant', () => {
+        expect(cc.rowCount).toBe(2);
+        expect(cc.value(0)).toBe(10);
+    });
+
+    it('arr', () => {
+        expect(arr.rowCount).toBe(4);
+        expect(arr.value(1)).toBe(2);
+        expect(arrWindow.value(0)).toBe(2);
+        expect(arrWindow.rowCount).toBe(2);
+    });
+
+    it('typed', () => {
+        expect(typedWindow.value(0)).toBe(2);
+        expect(typedWindow.rowCount).toBe(2);
+        expect(isTypedArray(typedWindow.toArray())).toBe(true);
+    });
+})
\ No newline at end of file
diff --git a/src/mol-base/collections/column.1.ts b/src/mol-base/collections/column.1.ts
new file mode 100644
index 000000000..d851a7c75
--- /dev/null
+++ b/src/mol-base/collections/column.1.ts
@@ -0,0 +1,191 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+interface Column<T> {
+    readonly '@type': Column.Type,
+    readonly '@array': ArrayLike<any> | undefined,
+
+    readonly isDefined: boolean,
+    readonly rowCount: number,
+    value(row: number): T,
+    valueKind(row: number): Column.ValueKind,
+    toArray(params?: Column.ToArrayParams): ReadonlyArray<T>,
+    stringEquals(row: number, value: string): boolean,
+    areValuesEqual(rowA: number, rowB: number): boolean
+}
+
+namespace Column {
+    export type Type = typeof Type.str | typeof Type.int | typeof Type.float | typeof Type.vector | typeof Type.matrix
+
+    export namespace Type {
+        export const str = { '@type': '' as string, kind: 'str' as 'str' };
+        export const int = { '@type': 0 as number, kind: 'int' as 'int' };
+        export const float = { '@type': 0 as number, kind: 'float' as 'float' };
+        export const vector = { '@type': [] as number[], kind: 'vector' as 'vector' };
+        export const matrix = { '@type': [] as number[][], kind: 'matrix' as 'matrix' };
+    }
+
+    export interface ToArrayParams {
+        array?: { new(size: number): ArrayLike<number> },
+        start?: number,
+        /** Last row (exclusive) */
+        end?: number
+    }
+
+    export interface ArraySpec<T extends Type> {
+        array: ArrayLike<T['@type']>,
+        type: T,
+        valueKind?: (row: number) => ValueKind
+    }
+
+    export const enum ValueKind { Present, NotPresent, Unknown }
+
+    export function Undefined<T extends Type>(rowCount: number, type: T): Column<T['@type']> {
+        return constColumn(type['@type'], rowCount, type, ValueKind.NotPresent);
+    }
+
+    export function ofConst<T extends Type>(v: T['@type'], rowCount: number, type: T): Column<T['@type']> {
+        return constColumn(v, rowCount, type, ValueKind.Present);
+    }
+
+    export function ofArray<T extends Column.Type>(spec: Column.ArraySpec<T>): Column<T['@type']> {
+        return arrayColumn(spec);
+    }
+
+    export function window<T>(column: Column<T>, start: number, end: number) {
+        return windowColumn(column, start, end);
+    }
+
+    /** Makes the column backned by an array. Useful for columns that accessed often. */
+    export function asArrayColumn<T>(c: Column<T>, array?: ToArrayParams['array']): Column<T> {
+        if (c['@array']) return c;
+        if (!c.isDefined) return Undefined(c.rowCount, c['@type']) as any as Column<T>;
+        return arrayColumn({ array: c.toArray({ array }), type: c['@type'] as any, valueKind: c.valueKind });
+    }
+}
+
+export default Column;
+
+function constColumn<T extends Column.Type>(v: T['@type'], rowCount: number, type: T, valueKind: Column.ValueKind): Column<T['@type']> {
+    const value: Column<T['@type']>['value'] = row => v;
+    return {
+        '@type': type,
+        '@array': void 0,
+        isDefined: valueKind === Column.ValueKind.Present,
+        rowCount,
+        value,
+        valueKind: row => valueKind,
+        toArray: params => {
+            const { array } = createArray(rowCount, params);
+            for (let i = 0, _i = array.length; i < _i; i++) array[i] = v;
+            return array;
+        },
+        stringEquals: type.kind === 'str'
+            ? (row, value) => value === v
+            : type.kind === 'float' || type.kind === 'int'
+                ? (row, value) => +value === v
+                : (row, value) => false,
+        areValuesEqual: (rowA, rowB) => true
+    }
+}
+
+function arrayColumn<T extends Column.Type>({ array, type, valueKind }: Column.ArraySpec<T>): Column<T['@type']> {
+    const rowCount = array.length;
+    const value: Column<T['@type']>['value'] = row => array[row];
+    const isTyped = isTypedArray(array);
+    return {
+        '@type': type,
+        '@array': array,
+        isDefined: true,
+        rowCount,
+        value,
+        valueKind: valueKind ? valueKind : row => Column.ValueKind.Present,
+        toArray: isTyped
+            ? params => typedArrayWindow(array, params) as any as ReadonlyArray<T>
+            : params => {
+                const { start, end } = getArrayBounds(rowCount, params);
+                if (start === 0 && end === array.length) return array as ReadonlyArray<T['@type']>;
+                const ret = new (params && typeof params.array !== 'undefined' ? params.array : (array as any).constructor)(end - start) as any;
+                for (let i = 0, _i = end - start; i < _i; i++) ret[i] = array[start + i];
+                return ret;
+            },
+        stringEquals: type.kind === 'int' || type.kind === 'float'
+            ? (row, value) => (array as any)[row] === +value
+            : type.kind === 'str'
+            ? (row, value) => array[row] === value
+            : (row, value) => false,
+        areValuesEqual: (rowA, rowB) => array[rowA] === array[rowB]
+    }
+}
+
+function windowColumn<T>(column: Column<T>, start: number, end: number) {
+    if (!column.isDefined) return Column.Undefined(end - start, column['@type']);
+    if (column['@array'] && isTypedArray(column['@array'])) return windowTyped(column, start, end);
+    return windowFull(column, start, end);
+}
+
+function windowTyped<T>(c: Column<T>, start: number, end: number): Column<T> {
+    const array = typedArrayWindow(c['@array'], { start, end });
+    return arrayColumn({ array, type: c['@type'], valueKind: c.valueKind }) as any;
+}
+
+function windowFull<T>(c: Column<T>, start: number, end: number): Column<T> {
+    const v = c.value, vk = c.valueKind, se = c.stringEquals, ave = c.areValuesEqual;
+    const value: Column<T>['value'] = start > 0 ? v : row => v(row + start);
+    const rowCount = end - start;
+    return {
+        '@type': c['@type'],
+        '@array': void 0,
+        isDefined: c.isDefined,
+        rowCount,
+        value,
+        valueKind: start > 0 ? vk : row => vk(row + start),
+        toArray: params => {
+            const { array } = createArray(rowCount, params);
+            for (let i = 0, _i = array.length; i < _i; i++) array[i] = v(i + start);
+            return array;
+        },
+        stringEquals: start > 0 ? se : (row, value) => se(row + start, value),
+        areValuesEqual: start > 0 ? ave : (rowA, rowB) => ave(rowA + start, rowB + start)
+    };
+}
+
+/** A helped function for Column.toArray */
+export function getArrayBounds(rowCount: number, params?: Column.ToArrayParams) {
+    const start = params && typeof params.start !== 'undefined' ? Math.max(Math.min(params.start, rowCount - 1), 0) : 0;
+    const end = params && typeof params.end !== 'undefined' ? Math.min(params.end, rowCount) : rowCount;
+    return { start, end };
+}
+
+/** A helped function for Column.toArray */
+export function createArray(rowCount: number, params?: Column.ToArrayParams) {
+    const c = params && typeof params.array !== 'undefined' ? params.array : Array;
+    const { start, end } = getArrayBounds(rowCount, params);
+    return { array: new c(end - start) as any[], start, end };
+}
+
+/** A helped function for Column.toArray */
+export function fillArrayValues(value: (row: number) => any, target: any[], start: number) {
+    for (let i = 0, _e = target.length; i < _e; i++) target[i] = value(start + i);
+    return target;
+}
+
+/** A helped function for Column.toArray */
+export function createAndFillArray(rowCount: number, value: (row: number) => any, params?: Column.ToArrayParams) {
+    const { array, start } = createArray(rowCount, params);
+    return fillArrayValues(value, array, start);
+}
+
+export function isTypedArray(data: any): boolean {
+    return !!data.buffer && typeof data.byteLength === 'number' && typeof data.BYTES_PER_ELEMENT === 'number';
+}
+
+export function typedArrayWindow(data: any, params?: Column.ToArrayParams): ReadonlyArray<number> {
+    const { constructor, buffer, length, byteOffset, BYTES_PER_ELEMENT } = data;
+    const { start, end } = getArrayBounds(length, params);
+    if (start === 0 && end === length) return data;
+    return new constructor(buffer, byteOffset + BYTES_PER_ELEMENT * start, Math.min(length, end - start));
+}
\ No newline at end of file
diff --git a/src/mol-base/collections/column.ts b/src/mol-base/collections/column.ts
index 0a9ec96f6..4aec52f9e 100644
--- a/src/mol-base/collections/column.ts
+++ b/src/mol-base/collections/column.ts
@@ -53,7 +53,7 @@ export function UndefinedColumn<T extends ColumnType>(rowCount: number, type: T)
     }
 }
 
-export function SingleValueColumn<T extends ColumnType>(v: T['@type'], rowCount: number, type: T): Column<T['@type']> {
+export function ConstColumn<T extends ColumnType>(v: T['@type'], rowCount: number, type: T): Column<T['@type']> {
     const value: Column<T['@type']>['value'] = row => v;
     return {
         '@type': type,
diff --git a/src/mol-base/collections/_spec/interval.spec.ts b/src/mol-base/collections/integer/_spec/interval.spec.ts
similarity index 98%
rename from src/mol-base/collections/_spec/interval.spec.ts
rename to src/mol-base/collections/integer/_spec/interval.spec.ts
index 015d54123..cda22df8e 100644
--- a/src/mol-base/collections/_spec/interval.spec.ts
+++ b/src/mol-base/collections/integer/_spec/interval.spec.ts
@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import Interval from '../integer/interval'
+import Interval from '../interval'
 
 describe('interval', () => {
     function testI(name: string, a: Interval, b: Interval) {
diff --git a/src/mol-base/collections/_spec/ordered-set.spec.ts b/src/mol-base/collections/integer/_spec/ordered-set.spec.ts
similarity index 99%
rename from src/mol-base/collections/_spec/ordered-set.spec.ts
rename to src/mol-base/collections/integer/_spec/ordered-set.spec.ts
index 28927b185..41b70f708 100644
--- a/src/mol-base/collections/_spec/ordered-set.spec.ts
+++ b/src/mol-base/collections/integer/_spec/ordered-set.spec.ts
@@ -4,8 +4,8 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import OrderedSet from '../integer/ordered-set'
-import Interval from '../integer/interval'
+import OrderedSet from '../ordered-set'
+import Interval from '../interval'
 
 describe('ordered set', () => {
     function ordSetToArray(set: OrderedSet) {
diff --git a/src/mol-base/collections/_spec/segmentation.spec.ts b/src/mol-base/collections/integer/_spec/segmentation.spec.ts
similarity index 86%
rename from src/mol-base/collections/_spec/segmentation.spec.ts
rename to src/mol-base/collections/integer/_spec/segmentation.spec.ts
index b44766e5f..c77fbb86f 100644
--- a/src/mol-base/collections/_spec/segmentation.spec.ts
+++ b/src/mol-base/collections/integer/_spec/segmentation.spec.ts
@@ -4,9 +4,9 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import OrderedSet from '../integer/ordered-set'
-import Interval from '../integer/interval'
-import Segmentation from '../integer/segmentation'
+import OrderedSet from '../ordered-set'
+import Interval from '../interval'
+import Segmentation from '../segmentation'
 
 describe('segments', () => {
     const data = OrderedSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]);
@@ -27,7 +27,7 @@ describe('segments', () => {
     })
 
     it('iteration', () => {
-        const it = Segmentation.segments(segs, data);
+        const it = Segmentation.transientSegments(segs, data);
 
         const t = Object.create(null);
         while (it.hasNext) {
diff --git a/src/mol-base/collections/_spec/sorted-array.spec.ts b/src/mol-base/collections/integer/_spec/sorted-array.spec.ts
similarity index 95%
rename from src/mol-base/collections/_spec/sorted-array.spec.ts
rename to src/mol-base/collections/integer/_spec/sorted-array.spec.ts
index 7aa731c9d..e3b729523 100644
--- a/src/mol-base/collections/_spec/sorted-array.spec.ts
+++ b/src/mol-base/collections/integer/_spec/sorted-array.spec.ts
@@ -4,8 +4,8 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import Interval from '../integer/interval'
-import SortedArray from '../integer/sorted-array'
+import Interval from '../interval'
+import SortedArray from '../sorted-array'
 
 describe('sortedArray', () => {
     function testI(name: string, a: Interval, b: Interval) {
diff --git a/src/mol-base/collections/_spec/int-tuple.spec.ts b/src/mol-base/collections/integer/_spec/tuple.spec.ts
similarity index 92%
rename from src/mol-base/collections/_spec/int-tuple.spec.ts
rename to src/mol-base/collections/integer/_spec/tuple.spec.ts
index 6ff7827c4..45b02a0ef 100644
--- a/src/mol-base/collections/_spec/int-tuple.spec.ts
+++ b/src/mol-base/collections/integer/_spec/tuple.spec.ts
@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import IntTuple from '../integer/tuple'
+import IntTuple from '../tuple'
 
 describe('int pair', () => {
     it('works', () => {
diff --git a/src/mol-base/collections/integer/segmentation.ts b/src/mol-base/collections/integer/segmentation.ts
index 6ecdbdf27..dda6b63a3 100644
--- a/src/mol-base/collections/integer/segmentation.ts
+++ b/src/mol-base/collections/integer/segmentation.ts
@@ -18,7 +18,8 @@ namespace Segmentation {
     export const getSegment: (segs: Segmentation, value: number) => number = Impl.getSegment as any;
     export const projectValue: (segs: Segmentation, set: OrderedSet, value: number) => Interval = Impl.projectValue as any;
 
-    export const segments: (segs: Segmentation, set: OrderedSet, range?: Interval) => Iterator<Segment> = Impl.segments as any;
+    // Segment iterator that mutates a single segment object to mark all the segments.
+    export const transientSegments: (segs: Segmentation, set: OrderedSet, range?: Interval) => Iterator<Segment> = Impl.segments as any;
 }
 
 interface Segmentation {
diff --git a/src/mol-data/model.ts b/src/mol-data/model.ts
index 54f89ceb9..689b170e5 100644
--- a/src/mol-data/model.ts
+++ b/src/mol-data/model.ts
@@ -14,16 +14,18 @@ import Segmentation from '../mol-base/collections/integer/segmentation'
  *
  * "Atoms" are integers in the range [0, atomCount).
  */
-interface Model {
+interface Model extends Readonly<{
     sourceData: Formats.RawData,
 
     common: CommonInterface,
     macromolecule: MacromoleculeInterface
 
     atomCount: number,
-    chains: Segmentation,
-    residues: Segmentation,
-    entities: Segmentation
-}
+    segments: Readonly<{
+        chains: Segmentation,
+        residues: Segmentation,
+        entities: Segmentation
+    }>
+}> { }
 
 export default Model
\ No newline at end of file
diff --git a/src/mol-data/model/builders/mmcif.ts b/src/mol-data/model/builders/mmcif.ts
index 71eff5052..f2318b794 100644
--- a/src/mol-data/model/builders/mmcif.ts
+++ b/src/mol-data/model/builders/mmcif.ts
@@ -57,9 +57,7 @@ function createModel(raw: RawData, data: mmCIF, bounds: Interval): Model {
         common: 0 as any,
         macromolecule: 0 as any,
         atomCount: Interval.size(bounds),
-        residues: segments.residues,
-        chains: segments.chains,
-        entities: segments.entities
+        segments
     };
 }
 
diff --git a/src/mol-data/structure.ts b/src/mol-data/structure.ts
index 26059084e..d4488fca5 100644
--- a/src/mol-data/structure.ts
+++ b/src/mol-data/structure.ts
@@ -40,6 +40,7 @@ export interface Unit extends Readonly<{
     getPosition(atom: number, slot: Vec3): Vec3
 }
 
+// TODO: do "single model" version of the structure?
 export interface Structure extends Readonly<{
     units: Readonly<{ [id: number]: Unit }>,
     atoms: AtomSet
diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts
index 4794a55cf..f5d4f394c 100644
--- a/src/perf-tests/sets.ts
+++ b/src/perf-tests/sets.ts
@@ -274,7 +274,7 @@ export namespace Tuples {
 export function testSegments() {
     const data = OrdSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]);
     const segs = Segmentation.create([0, 4, 10, 12, 13, 15, 25]);
-    const it = Segmentation.segments(segs, data);
+    const it = Segmentation.transientSegments(segs, data);
 
     while (it.hasNext) {
         const s = it.move();
-- 
GitLab