Skip to content

Commit

Permalink
wip refacoring MapSchema to use Map instead of plain object notation. #…
Browse files Browse the repository at this point in the history
  • Loading branch information
endel committed May 29, 2020
1 parent 3fc2dcc commit 19d604b
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 84 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"sinon": "^7.2.2",
"source-map-support": "^0.5.13",
"ts-node": "^7.0.1",
"typescript": "^3.6.4"
"typescript": "^3.9.3"
},
"nyc": {
"extension": [
Expand Down
9 changes: 9 additions & 0 deletions src/ChangeTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class ChangeTree {
parent: ChangeTree;
parentField: FieldKey;

// cached indexes for filtering
caches: {[field: number]: FieldCache} = {};

constructor(
Expand Down Expand Up @@ -74,6 +75,14 @@ export class ChangeTree {
}
}

delete (fieldName: FieldKey) {
const fieldIndex = this.fieldIndexes[fieldName];
const field = (typeof(fieldIndex) === "number") ? fieldIndex : fieldName;

this.changed = true;
this.changes.add(field);
}

mapIndex(instance: any, key: FieldKey) {
if (typeof instance === "object") {
if (!this.indexMap) {
Expand Down
48 changes: 32 additions & 16 deletions src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,21 +127,28 @@ export abstract class Schema {
Object.defineProperties(this, descriptors);
}

//
// Assign initial values
//
if (args[0]) {
Object.assign(this, args[0]);
this.assign(args[0]);
}
}

public assign(
props: { [prop in NonFunctionPropNames<this>]: this[prop] }
) {
Object.assign(this, props);
return this;
}

protected get _schema () { return (this.constructor as typeof Schema)._schema; }
protected get _descriptors () { return (this.constructor as typeof Schema)._descriptors; }
protected get _indexes () { return (this.constructor as typeof Schema)._indexes; }
protected get _fieldsByIndex() { return (this.constructor as typeof Schema)._fieldsByIndex }
protected get _filters () { return (this.constructor as typeof Schema)._filters; }
protected get _deprecated () { return (this.constructor as typeof Schema)._deprecated; }

get $changed () { return this.$changes.changed; }

public listen <K extends NonFunctionPropNames<this>>(attr: K, callback: (value: this[K], previousValue: this[K]) => void) {
if (!this.$listeners[attr as string]) {
this.$listeners[attr as string] = new EventEmitter();
Expand Down Expand Up @@ -304,7 +311,8 @@ export abstract class Schema {
// serializagion
let hasIndexChange = false;

const previousKeys = Object.keys(valueRef);
const previousKeys = Array.from(valueRef.keys());
console.log("DECODING MAP:", { value, valueRef, length, previousKeys });

for (let i = 0; i < length; i++) {
// `encodeAll` may indicate a higher number of indexes it actually encodes
Expand Down Expand Up @@ -336,14 +344,16 @@ export abstract class Schema {
let item;
let isNew = (!hasIndexChange && valueRef[newKey] === undefined) || (hasIndexChange && previousKey === undefined && hasMapIndex);

console.log({ newKey, isNew, hasMapIndex });

if (isNew && isSchemaType) {
item = this.createTypeInstance(bytes, it, type as typeof Schema);

} else if (previousKey !== undefined) {
item = valueRef[previousKey];
item = valueRef.get(previousKey);

} else {
item = valueRef[newKey]
item = valueRef.get(newKey);
}

if (isNilItem) {
Expand All @@ -364,15 +374,15 @@ export abstract class Schema {
}
}

delete value[newKey];
value.delete(newKey);
continue;

} else if (!isSchemaType) {
value[newKey] = decodePrimitiveType(type as string, bytes, it);
value.set(newKey, decodePrimitiveType(type as string, bytes, it));

} else {
item.decode(bytes, it);
value[newKey] = item;
value.set(newKey, item);
}

if (isNew) {
Expand Down Expand Up @@ -428,20 +438,17 @@ export abstract class Schema {
const schema = this._schema;
const indexes = this._indexes;
const fieldsByIndex = this._fieldsByIndex;
const filters = this._filters;
const changes = Array.from(
(encodeAll)
? this.$changes.allChanges
: this.$changes.changes
).sort();

for (let i = 0, l = changes.length; i < l; i++) {
const field = fieldsByIndex[changes[i]] || changes[i] as string;
const field = fieldsByIndex[changes[i]];
const _field = `_${field}`;

const type = schema[field];
const filter = (filters && filters[field]);
// const value = (filter && this.$allChanges[field]) || changes[field];
const value = this[_field];
const fieldIndex = indexes[field];

Expand All @@ -464,7 +471,6 @@ export abstract class Schema {
assertInstanceType(value, type as typeof Schema, this, field);

this.tryEncodeTypeId(bytes, type as typeof Schema, value.constructor as typeof Schema);

(value as Schema).encode(root, encodeAll, bytes, useFilters);
}

Expand Down Expand Up @@ -492,6 +498,8 @@ export abstract class Schema {

const isChildSchema = typeof(type[0]) !== "string";

console.log({ arrayChanges, numChanges, isChildSchema, arrayLength: value.length });

// assert ArraySchema was provided
assertInstanceType(this[_field], ArraySchema, this, field);

Expand All @@ -500,10 +508,12 @@ export abstract class Schema {
const index = arrayChanges[j];
const item = this[_field][index];

console.log({ index, item });

if (isChildSchema) { // is array of Schema
encode.number(bytes, index);

if (!encodeAll) {
if (!encodeAll) {
const indexChange = $changes.getIndexChange(item);
if (indexChange !== undefined) {
encode.uint8(bytes, INDEX_CHANGE);
Expand Down Expand Up @@ -546,15 +556,19 @@ export abstract class Schema {
const isChildSchema = typeof((type as any).map) !== "string";
const numChanges = keys.length;

console.log("ENCODE MAP =>", { keys, numChanges, previousKeys, isChildSchema });

// assert MapSchema was provided
assertInstanceType(this[_field], MapSchema, this, field);

for (let i = 0; i < numChanges; i++) {
const key = keys[i];
const item = this[_field][key];
const item = this[_field].get(key);

let mapItemIndex: number = undefined;

console.log("ENCODING MAP ITEM", { key, item });

if (encodeAll) {
if (item === undefined) {
// previously deleted items are skipped during `encodeAll`
Expand All @@ -576,6 +590,8 @@ export abstract class Schema {
mapItemIndex = (!$changes.isDeleted(key) || !item)
? this[_field]._indexes.get(key)
: undefined;

console.log({ indexChange, mapItemIndex });
}

const isNil = (item === undefined);
Expand Down
63 changes: 30 additions & 33 deletions src/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,8 @@ export function type (type: DefinitionType, context: Context = globalContext): P
/**
* skip if descriptor already exists for this field (`@deprecated()`)
*/
if (constructor._descriptors[field]) {
return;
}
if (constructor._descriptors[field]) { return; }

/**
* TODO: `isSchema` / `isArray` / `isMap` is repeated on many places!
* need to refactor all of them.
*/
const isArray = ArraySchema.is(type);
const isMap = !isArray && MapSchema.is(type);
const isSchema = Schema.is(type);
Expand All @@ -114,15 +108,24 @@ export function type (type: DefinitionType, context: Context = globalContext): P
/**
* Create Proxy for array or map items
*/
if (isArray || isMap) {
if (isArray && !(value instanceof ArraySchema)) {
value = new ArraySchema(...value);
}

if (isMap && !(value instanceof MapSchema)) {
value = new MapSchema(value);
}
// skip if value is the same as cached.
if (value === this[fieldCached]) {
return;
}

// automaticallty transform Array into ArraySchema
if (isArray && !(value instanceof ArraySchema)) {
value = new ArraySchema(...value);
}

// automaticallty transform Map into MapSchema
if (isMap && !(value instanceof MapSchema)) {
value = new MapSchema(value);
}

// if (isArray || isMap) {
if (isArray) {
value = new Proxy(value, {
get: (obj, prop) => obj[prop],
set: (obj, prop, setValue) => {
Expand All @@ -139,10 +142,6 @@ export function type (type: DefinitionType, context: Context = globalContext): P
obj.$changes.mapIndex(setValue, key);
}

// if (isMap) {
// obj._indexes.delete(prop);
// }

if (setValue instanceof Schema) {
// new items are flagged with all changes
if (!setValue.$changes.parent) {
Expand All @@ -156,10 +155,6 @@ export function type (type: DefinitionType, context: Context = globalContext): P

// apply change on ArraySchema / MapSchema
obj.$changes.change(key);

} else if (setValue !== obj[prop]) {
// console.log("SET NEW LENGTH:", setValue);
// console.log("PREVIOUS LENGTH: ", obj[prop]);
}

obj[prop] = setValue;
Expand Down Expand Up @@ -191,14 +186,11 @@ export function type (type: DefinitionType, context: Context = globalContext): P
});
}

// skip if value is the same as cached.
if (value === this[fieldCached]) {
return;
}

this[fieldCached] = value;

if (isArray) {
console.log("ASSIGNING AN ARRAY");

// directly assigning an array of items as value.
this.$changes.change(field);
value.$changes = new ChangeTree({}, field, this.$changes);
Expand All @@ -213,20 +205,25 @@ export function type (type: DefinitionType, context: Context = globalContext): P
}

} else if (isMap) {
console.log("ASSIGNING A MAP");

// directly assigning a map
value.$changes = new ChangeTree({}, field, this.$changes);
this.$changes.change(field);

for (let key in value) {
if (value[key] instanceof Schema) {
value[key].$changes = new ChangeTree(value[key]._indexes, key, value.$changes);
value[key].$changes.changeAll(value[key]);
(value as MapSchema).forEach((val, key) => {
console.log("FLAG AS CHANGED:", key);
if (val instanceof Schema) {
val.$changes = new ChangeTree(val._indexes, key, value.$changes);
val.$changes.changeAll(val);
}
value.$changes.mapIndex(value[key], key);
value.$changes.mapIndex(val, key);
value.$changes.change(key);
}
});

} else if (isSchema) {
console.log("ASSIGNING A SCHEMA");

// directly assigning a `Schema` object
// value may be set to null
this.$changes.change(field);
Expand Down
1 change: 1 addition & 0 deletions src/types/ArraySchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Schema } from "../Schema";
export class ArraySchema<T=any> extends Array<T> {
protected $sorting: boolean;
protected $changes: ChangeTree;
protected $cache: Map<number, T> = new Map<number, T>();

static get [Symbol.species](): any { return ArraySchema; }

Expand Down
Loading

0 comments on commit 19d604b

Please sign in to comment.