Skip to content

Commit

Permalink
feat(layered-storage): add a new storage for options
Browse files Browse the repository at this point in the history
This is intended to replace the prototype plus hacks insanity used at
the moment. However this is still WIP. There is no documentations yet
and more testing is necessary.

TODO:
- Add off.
- Write docs.
- Probably some other things too.

In a quick test I was able to resolve the issue discussed in
visjs/vis-network#178 and visjs/vis-network#213 with just a few lines of
code. Which is much better than the massive mess of weird hacks that
doesn't work reliably anyway.

Putting this to use will be a lot of work but fortunately it should be
possible to do it in parts. I would first use this in LayoutEngine and
EdgesHandler to resolve the forementioned issues and then probably one
module at the time.

Features:
- Encapsulates options merging.
- Explicit layer/segment/key structure instead of prototype chains.
- Observable.
- Overrides. *
- Type safety in TypeScript.

* Hierarchical layout is incompatible with smooth edges and has to
disable them. Overrides combined with observing easily and elegently
solve that. See the forementioned issues for current state.
  • Loading branch information
Thomaash committed Jan 26, 2020
1 parent cb12f54 commit d4476da
Show file tree
Hide file tree
Showing 19 changed files with 1,567 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ module.exports = {

// Empty functions are useful sometimes.
"@typescript-eslint/no-empty-function": "off",
// This would be great if TypeScript was perfect but sometimes tsc can't infer the correct type.
'@typescript-eslint/no-non-null-assertion': 'off',
// This is really crazy given the functions in this package.
"@typescript-eslint/no-explicit-any": "off",
// These are hoisted, I have no idea why it reports them by default.
Expand Down
93 changes: 93 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
"rollup-plugin-copy-glob": "^0.3.1",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^5.1.3",
"rollup-plugin-typescript2": "^0.25.3",
"semantic-release": "^16.0.0",
"sinon": "^8.0.1",
"snap-shot-it": "^7.9.1",
Expand Down
5 changes: 5 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import babel from "rollup-plugin-babel";
import commonjs from "rollup-plugin-commonjs";
import copyGlob from "rollup-plugin-copy-glob";
import resolve from "rollup-plugin-node-resolve";
import typescript from "rollup-plugin-typescript2";
import { generateHeader } from "vis-dev-utils";
import { terser } from "rollup-plugin-terser";

Expand All @@ -12,6 +13,10 @@ const commonPlugins = [
extensions: [".ts", ".js", ".json"]
}),
commonjs(),
typescript({
objectHashIgnoreUnknownHack: true,
tsconfig: "tsconfig.code.json"
}),
babel({
extensions: [".ts", ".js"],
runtimeHelpers: true
Expand Down
6 changes: 6 additions & 0 deletions src/layered-storage/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type KeyRange = number | string | symbol;
export type KeyValueLookup = Record<KeyRange, any>;
export type LayerRange = number;
export type Segment = boolean | number | object | string | symbol;

export type EventCallback<Key> = (keys: Key[]) => void;
124 changes: 124 additions & 0 deletions src/layered-storage/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { KeyValueLookup, LayerRange, Segment } from "./common";

const reverseNumeric = (a: number, b: number): number => b - a;

export class LayeredStorageCore<
KeyValue extends KeyValueLookup,
Layer extends LayerRange
> {
public monolithic = Symbol("Monolithic");

private _data = new Map<
Layer,
Map<Segment, Map<keyof KeyValue, KeyValue[keyof KeyValue]>>
>();

private _layers: Layer[] = [];
private readonly _segments = new Set<Segment>();

private readonly _topLevelCache = new Map<
Segment,
Map<keyof KeyValue, KeyValue[keyof KeyValue]>
>();

private _updateCache(key: keyof KeyValue): void {
segmentsLoop: for (const segment of this._segments) {
const sCache =
this._topLevelCache.get(segment) ||
this._topLevelCache.set(segment, new Map()).get(segment)!;

sCache.delete(key);

for (const layer of this._layers) {
const lsData = this._getLSData(layer, segment);
if (lsData.has(key)) {
sCache.set(key, lsData.get(key)!);
continue segmentsLoop;
}

const lmData = this._getLSData(layer, this.monolithic);
if (lmData.has(key)) {
sCache.set(key, lmData.get(key)!);
continue segmentsLoop;
}
}
}
}

private _getLSData(
layer: Layer,
segment: Segment
): Map<keyof KeyValue, KeyValue[keyof KeyValue]> {
let lData = this._data.get(layer);
if (lData == null) {
lData = new Map();
this._data.set(layer, lData);

this._layers = [...this._data.keys()].sort(reverseNumeric);
}

let lsData = lData.get(segment);
if (lsData == null) {
lsData = new Map();
lData.set(segment, lsData);

this._segments.add(segment);
}

return lsData;
}

public get<Key extends keyof KeyValue>(
segment: Segment,
key: Key
): KeyValue[Key] | undefined {
const sData = this._topLevelCache.get(segment);
if (sData == null) {
return;
}

return sData.get(key);
}

public has<Key extends keyof KeyValue>(segment: Segment, key: Key): boolean {
const sData = this._topLevelCache.get(segment);
if (sData == null) {
return false;
}

return sData.has(key);
}

public set<Key extends keyof KeyValue>(
layer: Layer,
segment: Segment,
key: Key,
value: KeyValue[Key]
): void {
const lsData = this._getLSData(layer, segment);
lsData.set(key, value);

this._updateCache(key);
}

public delete<Key extends keyof KeyValue>(
layer: Layer,
segment: Segment,
key: Key
): boolean {
const lsData = this._getLSData(layer, segment);
const didItExist = lsData.delete(key);

this._updateCache(key);

return didItExist;
}

public deleteSegmentData(segment: Segment): void {
for (const lData of this._data.values()) {
lData.delete(segment);
}
this._topLevelCache.delete(segment);
this._segments.delete(segment);
}
}
7 changes: 7 additions & 0 deletions src/layered-storage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export {
LayeredStorage,
LayeredStorageSegmentTransaction,
LayeredStorageTransaction
} from "./layered-storage";
export { LayeredStorageSegment } from "./segment";
export { EventCallback, KeyValueLookup, LayerRange, Segment } from "./common";
Loading

0 comments on commit d4476da

Please sign in to comment.