Skip to content

Commit b43742c

Browse files
committed
upgrade chache update algorithm
1 parent 4905a4e commit b43742c

File tree

10 files changed

+89
-39
lines changed

10 files changed

+89
-39
lines changed

bun.lockb

372 Bytes
Binary file not shown.

exampleVault/View Fields/View Field.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ Self Loop Error: `VIEW[**{computed}**][text():computed]`
5757
## Links
5858

5959
`INPUT[suggester(optionQuery(#example-note), useLinks(false)):file]`
60-
`VIEW[\[\[{file}|link\]\]][text(renderMarkdown)]`
61-
`VIEW[{file}][link]`
60+
link with render markdown: `VIEW[\[\[{file}|link\]\]][text(renderMarkdown)]`
61+
link with link view field: `VIEW[{file}][link]`
6262

6363
```meta-bind
6464
INPUT[imageSuggester(optionQuery("Other/Images")):image]

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@
5555
"dependencies": {
5656
"@codemirror/legacy-modes": "^6.3.3",
5757
"@lemons_dev/parsinom": "^0.0.12",
58-
"svelte": "^4.2.11",
59-
"itertools-ts": "^1.27.0",
60-
"mathjs": "^12.0.0",
58+
"itertools-ts": "^1.27.1",
59+
"mathjs": "^12.4.1",
60+
"moment": "^2.30.1",
61+
"svelte": "^4.2.12",
6162
"zod": "^3.22.4",
62-
"zod-validation-error": "^2.1.0",
63-
"moment": "^2.30.1"
63+
"zod-validation-error": "^2.1.0"
6464
},
6565
"private": true
6666
}

packages/core/src/metadata/InternalMetadataSources.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,13 @@ export class GlobalMetadataSource implements IMetadataSource<GlobalMetadataCache
125125
return this.cache;
126126
}
127127

128-
public updateCache(value: unknown, bindTarget: BindTargetDeclaration): GlobalMetadataCacheItem {
128+
public writeCache(value: unknown, bindTarget: BindTargetDeclaration): GlobalMetadataCacheItem {
129129
PropUtils.setAndCreate(this.cache.data, bindTarget.storageProp, value);
130130

131131
return this.cache;
132132
}
133133

134-
public updateEntireCache(value: Metadata, cacheItem: GlobalMetadataCacheItem): void {
134+
public writeEntireCache(value: Metadata, cacheItem: GlobalMetadataCacheItem): void {
135135
cacheItem.data = value;
136136
}
137137

@@ -239,15 +239,15 @@ export class ScopeMetadataSource implements IMetadataSource<IMetadataCacheItem>
239239
});
240240
}
241241

242-
public updateCache(_value: unknown, _bindTarget: BindTargetDeclaration): IMetadataCacheItem {
242+
public writeCache(_value: unknown, _bindTarget: BindTargetDeclaration): IMetadataCacheItem {
243243
throw new MetaBindInternalError({
244244
errorLevel: ErrorLevel.CRITICAL,
245245
effect: 'action not permitted',
246246
cause: `source 'scope' should have no cache items or subscriptions`,
247247
});
248248
}
249249

250-
public updateEntireCache(_value: Metadata, _cacheItem: IMetadataCacheItem): void {
250+
public writeEntireCache(_value: Metadata, _cacheItem: IMetadataCacheItem): void {
251251
// noop
252252
}
253253

packages/core/src/metadata/MetadataManager.ts

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import { type IMetadataSource, type Metadata } from 'packages/core/src/metadata/
99
import { MetadataSubscription } from 'packages/core/src/metadata/MetadataSubscription';
1010
import { type BindTargetDeclaration } from 'packages/core/src/parsers/bindTargetParser/BindTargetDeclaration';
1111
import { type Signal } from 'packages/core/src/utils/Signal';
12-
import { areArraysEqual, arrayStartsWith } from 'packages/core/src/utils/Utils';
1312
import {
1413
ErrorLevel,
1514
MetaBindBindTargetError,
1615
MetaBindInternalError,
1716
} from 'packages/core/src/utils/errors/MetaBindErrors';
17+
import { PropUtils } from 'packages/core/src/utils/prop/PropUtils';
18+
import { type PropPath } from 'packages/core/src/utils/prop/PropPath';
1819

1920
export const METADATA_CACHE_UPDATE_CYCLE_THRESHOLD = 5; // {syncInterval (200)} * 5 = 1s
2021
export const METADATA_CACHE_INACTIVE_CYCLE_THRESHOLD = 5 * 60; // {syncInterval (200)} * 5 * 60 = 1 minute
@@ -40,34 +41,40 @@ export function hasUpdateOverlap(a: BindTargetDeclaration | undefined, b: BindTa
4041
return false;
4142
}
4243

43-
return metadataPathHasUpdateOverlap(
44-
a.storageProp.toStringArray(),
45-
b.storageProp.toStringArray(),
46-
b.listenToChildren,
47-
);
44+
// TODO: this can be faster, no need to create new arrays
45+
return metadataPathHasUpdateOverlap(a.storageProp, b.storageProp, b.listenToChildren);
4846
}
4947

5048
/**
5149
* Checks if path `b` should receive an update when path `a` changes.
5250
* The rules are as follows:
53-
* - if they are equal
54-
* - if `b` starts with `a` (`a = foo.bar` and `b = foo.bar.baz`)
55-
* - if `b` has `listenToChildren` and `a` starts with `b` (`a = foo.bar.baz` and `b = foo.bar`)
51+
*
52+
* - b = foo.bar.baz
53+
* - a = foo.bar -> true
54+
* - a = foo.bar.baz -> true
55+
* - a = foo.bar.baz.baz -> true
56+
* - a = foo.bar.foo -> false
57+
* - a = foo.foo -> false
5658
*
5759
* @param a
5860
* @param b
5961
* @param listenToChildren whether b listens to updates in children
6062
*/
61-
export function metadataPathHasUpdateOverlap(a: string[], b: string[], listenToChildren: boolean): boolean {
62-
if (areArraysEqual(a, b)) {
63-
return true;
63+
export function metadataPathHasUpdateOverlap(a: PropPath, b: PropPath, listenToChildren: boolean): boolean {
64+
const aPath = a.path;
65+
const bPath = b.path;
66+
67+
for (let i = 0; i < Math.min(aPath.length, bPath.length); i++) {
68+
if (aPath[i].type !== bPath[i].type || aPath[i].prop !== bPath[i].prop) {
69+
return false;
70+
}
6471
}
6572

66-
if (arrayStartsWith(b, a)) {
73+
if (aPath.length > bPath.length) {
74+
return listenToChildren;
75+
} else {
6776
return true;
6877
}
69-
70-
return listenToChildren && arrayStartsWith(a, b);
7178
}
7279

7380
export function bindTargetToString(a: BindTargetDeclaration | undefined): string {
@@ -359,12 +366,12 @@ export class MetadataManager {
359366
if (source === undefined) {
360367
throw new MetaBindInternalError({
361368
errorLevel: ErrorLevel.ERROR,
362-
effect: 'can not update metadata',
369+
effect: 'can not write to cache',
363370
cause: `Source "${bindTarget.storageType}" does not exist`,
364371
});
365372
}
366373

367-
const cacheItem = source.updateCache(value, bindTarget);
374+
const cacheItem = source.writeCache(value, bindTarget);
368375
cacheItem.pendingInternalChange = true;
369376
cacheItem.cyclesSinceInternalChange = 0;
370377
this.notifyListeners(bindTarget, updateSourceUuid);
@@ -431,8 +438,8 @@ export class MetadataManager {
431438

432439
if (
433440
metadataPathHasUpdateOverlap(
434-
bindTarget.storageProp.toStringArray(),
435-
cacheSubscription.bindTarget.storageProp.toStringArray(),
441+
bindTarget.storageProp,
442+
cacheSubscription.bindTarget.storageProp,
436443
cacheSubscription.bindTarget.listenToChildren,
437444
)
438445
) {
@@ -501,8 +508,24 @@ export class MetadataManager {
501508
return;
502509
}
503510

504-
source.updateEntireCache(value, cacheItem);
505-
this.notifyAllListeners(source, cacheItem);
511+
const oldValue = source.readEntireCacheItem(cacheItem);
512+
513+
source.writeEntireCache(value, cacheItem);
514+
515+
for (const subscription of cacheItem.subscriptions) {
516+
if (subscription.bindTarget === undefined) {
517+
continue;
518+
}
519+
520+
const propPath = subscription.bindTarget.storageProp;
521+
522+
const newBoundValue = PropUtils.tryGet(value, propPath);
523+
const oldBoundValue = PropUtils.tryGet(oldValue, propPath);
524+
525+
if (newBoundValue !== oldBoundValue) {
526+
subscription.notify(newBoundValue);
527+
}
528+
}
506529
}
507530

508531
/**

packages/core/src/metadata/MetadataSource.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export interface IMetadataSource<T extends IMetadataCacheItem> {
118118
* @param bindTarget
119119
* @returns The cache item for the bind target.
120120
*/
121-
updateCache(value: unknown, bindTarget: BindTargetDeclaration): T;
121+
writeCache(value: unknown, bindTarget: BindTargetDeclaration): T;
122122

123123
/**
124124
* Updates the entire cache item with the given value.
@@ -127,7 +127,7 @@ export interface IMetadataSource<T extends IMetadataCacheItem> {
127127
* @param value
128128
* @param cacheItem
129129
*/
130-
updateEntireCache(value: Metadata, cacheItem: T): void;
130+
writeEntireCache(value: Metadata, cacheItem: T): void;
131131

132132
/**
133133
* Reads the cache item for the given bind target.
@@ -277,15 +277,15 @@ export abstract class FilePathMetadataSource<T extends FilePathMetadataCacheItem
277277

278278
abstract syncExternal(cacheItem: T): void;
279279

280-
updateCache(value: unknown, bindTarget: BindTargetDeclaration): T {
280+
writeCache(value: unknown, bindTarget: BindTargetDeclaration): T {
281281
const cacheItem = this.getOrCreateCacheItem(bindTarget.storagePath);
282282

283283
PropUtils.setAndCreate(cacheItem.data, bindTarget.storageProp, value);
284284

285285
return cacheItem;
286286
}
287287

288-
updateEntireCache(value: Metadata, cacheItem: T): void {
288+
writeEntireCache(value: Metadata, cacheItem: T): void {
289289
cacheItem.data = value;
290290
}
291291

packages/core/src/utils/prop/PropPath.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,19 @@ export class PropPath {
9393
concat(path: PropPath): PropPath {
9494
return new PropPath(this.path.concat(path.path));
9595
}
96+
97+
compareDiffArray(path: (string | number)[]): boolean {
98+
const len = Math.min(this.path.length, path.length);
99+
100+
for (let i = 0; i < len; i++) {
101+
const access = this.path[i];
102+
const pathElement = path[i];
103+
104+
if (access.prop !== pathElement.toString()) {
105+
return false;
106+
}
107+
}
108+
109+
return true;
110+
}
96111
}

tests/__mocks__/TestPlugin.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export class TestPlugin implements IPlugin {
151151
return this.testInputFields.map(x => x.field.getDefaultValue());
152152
}
153153

154-
getAllTestInputFieldSpys(): unknown[] {
154+
getAllTestInputFieldSpys(): Mock<(value: any) => void>[] {
155155
return this.testInputFields.map(x => x.fieldSignalSetSpy);
156156
}
157157

@@ -169,6 +169,16 @@ export class TestPlugin implements IPlugin {
169169
}
170170
}
171171

172+
expectAllTestInputFieldSpysToHaveBeenCalledTimesOrLess(times: number[]): void {
173+
if (times.length !== this.testInputFields.length) {
174+
throw new Error('Invalid times length');
175+
}
176+
177+
for (const [spy, time] of this.getAllTestInputFieldSpys().map((x, i) => [x, times[i]] as const)) {
178+
expect(spy.mock.calls.length).toBeLessThanOrEqual(time);
179+
}
180+
}
181+
172182
expectAllTestInputFieldValuesToEqual(values: any[]): void {
173183
if (values.length !== this.testInputFields.length) {
174184
throw new Error('Invalid values length');

tests/__preload__/happydom.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ GlobalRegistrator.register({
88
if (process.env.LOG_TESTS === 'false') {
99
console.log = () => {};
1010
console.debug = () => {};
11+
} else {
12+
console.debug = () => {};
1113
}

tests/fields/IPF.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,8 +536,8 @@ describe('IPF', () => {
536536

537537
// check that the input field value is the default value of the IPF, since the front-matter value is invalid
538538
testPlugin.expectAllTestInputFieldValuesToEqual([DEFAULT_VALUE_INDICATOR]);
539-
// check that the value of the IPF has only been set once
540-
testPlugin.expectAllTestInputFieldSpysToHaveBeenCalledTimes([1]);
539+
// check that the value of the IPF has only been set once or less, if the value is equal to the default value
540+
testPlugin.expectAllTestInputFieldSpysToHaveBeenCalledTimesOrLess([1]);
541541
// check that the input field did not update the metadata manager
542542
testPlugin.expectMetadataManagerToHaveUpdatedTimes(0);
543543
});

0 commit comments

Comments
 (0)