Skip to content

Commit 3f4a864

Browse files
Merge pull request #33 from viamrobotics/memoize-1
Memoize useResourceNames results
2 parents 1cccf1b + 4281483 commit 3f4a864

File tree

3 files changed

+94
-17
lines changed

3 files changed

+94
-17
lines changed

.changeset/clear-eyes-battle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@viamrobotics/svelte-sdk': patch
3+
---
4+
5+
Memoize useResourceNames results

src/lib/hooks/resource-names.svelte.ts

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,53 @@ interface Context {
2525
current: Record<PartID, Query | undefined>;
2626
}
2727

28+
const revisions = new Map<string, string>();
29+
30+
const deepEqualResourceNames = (
31+
a: ResourceName[],
32+
b: ResourceName[]
33+
): boolean => {
34+
if (a.length !== b.length) {
35+
return false;
36+
}
37+
38+
return a.every((item, i) => JSON.stringify(item) === JSON.stringify(b[i]));
39+
};
40+
41+
/**
42+
* sorts resource names by local/remote -> type -> name (alphabetical)
43+
* to produce a list like:
44+
*
45+
* component a
46+
* component z
47+
* service b
48+
* component remote:c
49+
* service remote:b
50+
* @param resourceNames
51+
*/
52+
const sortResourceNames = (resourceNames: ResourceName[]) => {
53+
resourceNames.sort(({ type, name }, { type: otherType, name: otherName }) => {
54+
// sort all non-remote resources before remote resources
55+
if (name.includes(':') !== otherName.includes(':')) {
56+
return name.includes(':') ? 1 : -1;
57+
}
58+
59+
// sort alphabetically within type
60+
// sort components before services
61+
return type === otherType
62+
? name.localeCompare(otherName)
63+
: type.localeCompare(otherType);
64+
});
65+
};
66+
2867
export const provideResourceNamesContext = () => {
2968
const machineStatuses = useMachineStatuses();
3069
const clients = useRobotClients();
3170

71+
const partIDs = $derived(Object.keys(clients.current));
3272
const options = $derived(
33-
Object.entries(clients.current).map(([partID, client]) => {
34-
const revision =
35-
machineStatuses.current[partID]?.data?.config?.revision ?? '';
36-
73+
partIDs.map((partID) => {
74+
const client = clients.current[partID];
3775
return queryOptions({
3876
enabled: client !== undefined,
3977
queryKey: [
@@ -42,14 +80,16 @@ export const provideResourceNamesContext = () => {
4280
partID,
4381
'robotClient',
4482
'resourceNames',
45-
revision,
4683
],
84+
staleTime: Infinity,
4785
queryFn: async () => {
4886
if (!client) {
4987
throw new Error('No client');
5088
}
5189

52-
return client.resourceNames();
90+
const resourceNames = await client.resourceNames();
91+
sortResourceNames(resourceNames);
92+
return resourceNames;
5393
},
5494
});
5595
})
@@ -58,18 +98,40 @@ export const provideResourceNamesContext = () => {
5898
const queries = fromStore(
5999
createQueries({
60100
queries: toStore(() => options),
61-
combine: (results) => {
62-
const partIDs = Object.keys(clients.current);
63-
return Object.fromEntries(
64-
results.map((result, index) => [partIDs[index], result])
65-
);
66-
},
67101
})
68102
);
69103

104+
/**
105+
* Individually refetch part resource names based on revision
106+
*/
107+
$effect(() => {
108+
let index = 0;
109+
110+
for (const partID of partIDs) {
111+
const revision =
112+
machineStatuses.current[partID]?.data?.config?.revision ?? '';
113+
const lastRevision = revisions.get(partID);
114+
revisions.set(partID, revision);
115+
116+
if (!lastRevision) continue;
117+
118+
if (revision !== lastRevision) {
119+
queries.current[index]?.refetch();
120+
}
121+
122+
index += 1;
123+
}
124+
});
125+
126+
const current = $derived(
127+
Object.fromEntries(
128+
queries.current.map((result, index) => [partIDs[index], result])
129+
)
130+
);
131+
70132
setContext<Context>(key, {
71133
get current() {
72-
return queries.current;
134+
return current;
73135
},
74136
});
75137
};
@@ -84,15 +146,26 @@ export const useResourceNames = (
84146
const resourceSubtype = $derived(
85147
typeof subtype === 'function' ? subtype() : subtype
86148
);
149+
const error = $derived(query?.error ?? undefined);
150+
const fetching = $derived(query?.isFetching ?? true);
151+
87152
const filtered = $derived(
88153
subtype ? data.filter((value) => value.subtype === resourceSubtype) : data
89154
);
90-
const error = $derived(query?.error ?? undefined);
91-
const fetching = $derived(query?.isFetching ?? true);
155+
156+
let current = $state.raw<ResourceName[]>([]);
157+
let last: ResourceName[] = [];
158+
159+
$effect.pre(() => {
160+
if (!deepEqualResourceNames(last, filtered)) {
161+
last = current;
162+
current = filtered;
163+
}
164+
});
92165

93166
return {
94167
get current() {
95-
return filtered;
168+
return current;
96169
},
97170
get error() {
98171
return error;

src/routes/+layout.svelte

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ let dialConfigs = $derived(
2626
Object.fromEntries(Object.entries(configs).filter(([key]) => enabled[key]))
2727
);
2828
29-
$inspect(dialConfigs);
3029
let { children }: Props = $props();
3130
</script>
3231

0 commit comments

Comments
 (0)