Skip to content

Commit 46b2033

Browse files
committed
Simplify schema loading, fixes flashing on game switch
1 parent 80b924d commit 46b2033

File tree

8 files changed

+155
-209
lines changed

8 files changed

+155
-209
lines changed

src/components/DeclarationsPage.tsx

Lines changed: 3 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,26 @@
11
import React, { useCallback, useMemo, useState } from "react";
22
import { styled } from "@linaria/react";
3-
import {
4-
DeclarationsContext,
5-
DeclarationsContextType,
6-
ReferenceEntry,
7-
declarationKey,
8-
} from "./schema/DeclarationsContext";
9-
import { Declaration, SchemaClass, SchemaFieldType } from "../data/types";
10-
import { GameId } from "../games-list";
3+
import { DeclarationsContext, DeclarationsContextType } from "./schema/DeclarationsContext";
114
import { DeclarationsSidebar } from "./DeclarationsSidebar";
125
import { ContentList } from "./schema/ContentList";
136
import { SidebarFilterContext } from "./layout/SidebarFilterContext";
147
import { SearchContext } from "./search/SearchContext";
158
import { useHashParam } from "../utils/filtering";
169
import { NavBar } from "./layout/NavBar";
1710

18-
function collectTypeKeys(type: SchemaFieldType, out: Set<string>) {
19-
switch (type.category) {
20-
case "declared_class":
21-
case "declared_enum":
22-
out.add(declarationKey(type.module, type.name));
23-
break;
24-
case "ptr":
25-
case "fixed_array":
26-
collectTypeKeys(type.inner, out);
27-
break;
28-
case "atomic":
29-
if (type.inner) collectTypeKeys(type.inner, out);
30-
if (type.inner2) collectTypeKeys(type.inner2, out);
31-
break;
32-
}
33-
}
34-
35-
function buildReferences(declarations: Declaration[]): Map<string, ReferenceEntry[]> {
36-
const refs = new Map<string, ReferenceEntry[]>();
37-
38-
function addRef(target: string, entry: ReferenceEntry) {
39-
let list = refs.get(target);
40-
if (!list) {
41-
list = [];
42-
refs.set(target, list);
43-
}
44-
list.push(entry);
45-
}
46-
47-
for (const decl of declarations) {
48-
if (decl.kind === "class") {
49-
for (const parent of decl.parents) {
50-
addRef(declarationKey(parent.module, parent.name), {
51-
declarationName: decl.name,
52-
declarationModule: decl.module,
53-
relation: "class",
54-
});
55-
}
56-
for (const field of decl.fields) {
57-
const keys = new Set<string>();
58-
collectTypeKeys(field.type, keys);
59-
const declKey = declarationKey(decl.module, decl.name);
60-
for (const key of keys) {
61-
if (key !== declKey) {
62-
addRef(key, {
63-
declarationName: decl.name,
64-
declarationModule: decl.module,
65-
fieldName: field.name,
66-
relation: "field",
67-
});
68-
}
69-
}
70-
}
71-
}
72-
}
73-
74-
return refs;
75-
}
76-
77-
export default function DeclarationsPage({
78-
context,
79-
}: {
80-
context: Omit<DeclarationsContextType, "references" | "otherGamesLookup" | "classesByKey"> & {
81-
otherGames: Map<GameId, Declaration[]>;
82-
};
83-
}) {
11+
export default function DeclarationsPage({ context }: { context: DeclarationsContextType }) {
8412
const [filter, setFilter] = useState("");
8513
const [sidebarOpen, setSidebarOpen] = useState(false);
8614
const search = useHashParam("search") ?? "";
8715

88-
const references = useMemo(() => buildReferences(context.declarations), [context.declarations]);
89-
90-
const classesByKey = useMemo(() => {
91-
const map = new Map<string, SchemaClass>();
92-
for (const d of context.declarations) {
93-
if (d.kind === "class") map.set(declarationKey(d.module, d.name), d);
94-
}
95-
return map;
96-
}, [context.declarations]);
97-
98-
const otherGamesLookup = useMemo(() => {
99-
const lookup = new Map<GameId, Map<string, Declaration>>();
100-
for (const [gameId, decls] of context.otherGames) {
101-
const map = new Map<string, Declaration>();
102-
for (const d of decls) map.set(d.name, d);
103-
lookup.set(gameId, map);
104-
}
105-
return lookup;
106-
}, [context.otherGames]);
107-
108-
const fullContext = useMemo(() => {
109-
const { otherGames: _, ...rest } = context;
110-
return { ...rest, references, classesByKey, otherGamesLookup };
111-
}, [context, references, classesByKey, otherGamesLookup]);
112-
11316
const searchCtx = useMemo(() => ({ search }), [search]);
11417
const filterCtx = useMemo(() => ({ filter, setFilter }), [filter]);
11518

11619
const closeSidebar = useCallback(() => setSidebarOpen(false), []);
11720
const openSidebar = useCallback(() => setSidebarOpen(true), []);
11821

11922
return (
120-
<DeclarationsContext.Provider value={fullContext}>
23+
<DeclarationsContext.Provider value={context}>
12124
<SearchContext.Provider value={searchCtx}>
12225
<SidebarFilterContext.Provider value={filterCtx}>
12326
<PageGrid>

src/components/schema/ContentList.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ function renderItem(declaration: Declaration, isSearchResult?: boolean) {
234234
const renderSearchResult = (declaration: Declaration) => renderItem(declaration, true);
235235

236236
export function ContentList() {
237-
const { declarations, metadata, loading, error } = useContext(DeclarationsContext);
237+
const { declarations, metadata, error } = useContext(DeclarationsContext);
238238
const { data, isSearching } = useFilteredData(declarations);
239239
const { module } = useParams();
240240

@@ -252,9 +252,7 @@ export function ContentList() {
252252
<OtherGamesResults />
253253
</>
254254
) : error ? (
255-
<TextMessage>{`Failed to load schemas: ${error}`}</TextMessage>
256-
) : loading ? (
257-
<TextMessage>Loading schemas...</TextMessage>
255+
<TextMessage>{error}</TextMessage>
258256
) : (
259257
<>
260258
<TextMessage>Choose a class or enum from the sidebar, or use search...</TextMessage>
Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
import { createContext } from "react";
22
import { href } from "react-router";
33
import { Declaration, SchemaClass } from "../../data/types";
4-
import { SchemaMetadata } from "../../data/loader";
4+
import { SchemaMetadata } from "../../data/schemas";
55
import { GameId } from "../../games-list";
6-
7-
export type ReferenceEntry = {
8-
declarationName: string;
9-
declarationModule: string;
10-
fieldName?: string;
11-
relation: "field" | "class";
12-
};
13-
14-
export function declarationKey(module: string, name: string): string {
15-
return `${module}/${name}`;
16-
}
6+
import type { ReferenceEntry } from "../../data/derived";
7+
export { declarationKey, type ReferenceEntry } from "../../data/derived";
178

189
export function declarationPath(game: string, module: string, name: string): string {
1910
return href("/:game/:module?/:scope?", { game, module, scope: name });
@@ -26,7 +17,6 @@ export type DeclarationsContextType = {
2617
metadata: SchemaMetadata;
2718
references: Map<string, ReferenceEntry[]>;
2819
otherGamesLookup: Map<GameId, Map<string, Declaration>>;
29-
loading: boolean;
3020
error: string | null;
3121
};
3222

@@ -37,6 +27,5 @@ export const DeclarationsContext = createContext<DeclarationsContextType>({
3727
metadata: { revision: 0, versionDate: "", versionTime: "" },
3828
references: new Map(),
3929
otherGamesLookup: new Map(),
40-
loading: false,
4130
error: null,
4231
});

src/data/derived.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import type { Declaration, SchemaClass, SchemaFieldType } from "./types";
2+
import type { GameId } from "../games-list";
3+
import { GAME_LIST } from "../games-list";
4+
import { preloadedData } from "./preload";
5+
6+
export type ReferenceEntry = {
7+
declarationName: string;
8+
declarationModule: string;
9+
fieldName?: string;
10+
relation: "field" | "class";
11+
};
12+
13+
export function declarationKey(module: string, name: string): string {
14+
return `${module}/${name}`;
15+
}
16+
17+
function collectTypeKeys(type: SchemaFieldType, out: Set<string>) {
18+
switch (type.category) {
19+
case "declared_class":
20+
case "declared_enum":
21+
out.add(declarationKey(type.module, type.name));
22+
break;
23+
case "ptr":
24+
case "fixed_array":
25+
collectTypeKeys(type.inner, out);
26+
break;
27+
case "atomic":
28+
if (type.inner) collectTypeKeys(type.inner, out);
29+
if (type.inner2) collectTypeKeys(type.inner2, out);
30+
break;
31+
}
32+
}
33+
34+
function buildReferences(declarations: Declaration[]): Map<string, ReferenceEntry[]> {
35+
const refs = new Map<string, ReferenceEntry[]>();
36+
37+
function addRef(target: string, entry: ReferenceEntry) {
38+
let list = refs.get(target);
39+
if (!list) {
40+
list = [];
41+
refs.set(target, list);
42+
}
43+
list.push(entry);
44+
}
45+
46+
for (const decl of declarations) {
47+
if (decl.kind === "class") {
48+
for (const parent of decl.parents) {
49+
addRef(declarationKey(parent.module, parent.name), {
50+
declarationName: decl.name,
51+
declarationModule: decl.module,
52+
relation: "class",
53+
});
54+
}
55+
for (const field of decl.fields) {
56+
const keys = new Set<string>();
57+
collectTypeKeys(field.type, keys);
58+
const declKey = declarationKey(decl.module, decl.name);
59+
for (const key of keys) {
60+
if (key !== declKey) {
61+
addRef(key, {
62+
declarationName: decl.name,
63+
declarationModule: decl.module,
64+
fieldName: field.name,
65+
relation: "field",
66+
});
67+
}
68+
}
69+
}
70+
}
71+
}
72+
73+
return refs;
74+
}
75+
76+
export type DerivedGameData = {
77+
classesByKey: Map<string, SchemaClass>;
78+
references: Map<string, ReferenceEntry[]>;
79+
otherGamesLookup: Map<GameId, Map<string, Declaration>>;
80+
};
81+
82+
const cache = new Map<GameId, DerivedGameData>();
83+
84+
export function getDerivedGameData(gameId: GameId): DerivedGameData {
85+
const cached = cache.get(gameId);
86+
if (cached) return cached;
87+
88+
const schema = preloadedData.get(gameId);
89+
const declarations = schema?.declarations ?? [];
90+
91+
const classesByKey = new Map<string, SchemaClass>();
92+
for (const d of declarations) {
93+
if (d.kind === "class") classesByKey.set(declarationKey(d.module, d.name), d);
94+
}
95+
96+
const otherGamesLookup = new Map<GameId, Map<string, Declaration>>();
97+
for (const g of GAME_LIST) {
98+
if (g.id === gameId) continue;
99+
const other = preloadedData.get(g.id);
100+
if (other) {
101+
const map = new Map<string, Declaration>();
102+
for (const d of other.declarations) map.set(d.name, d);
103+
otherGamesLookup.set(g.id, map);
104+
}
105+
}
106+
107+
const result: DerivedGameData = {
108+
classesByKey,
109+
references: buildReferences(declarations),
110+
otherGamesLookup,
111+
};
112+
113+
cache.set(gameId, result);
114+
return result;
115+
}

src/data/loader.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
import { Declaration } from "./types";
21
import { GameId } from "../games-list";
3-
import { parseSchemas, type SchemasJson, type SchemaMetadata } from "./schemas";
4-
export type { SchemasJson, SchemaMetadata };
2+
import { parseSchemas, type SchemasJson } from "./schemas";
53

64
const schemaUrls = import.meta.glob<string>("../../schemas/*.json.gz", {
75
import: "default",
86
query: "?url",
97
eager: true,
108
});
119

12-
const cache = new Map<GameId, Promise<{ declarations: Declaration[]; metadata: SchemaMetadata }>>();
10+
type LoadResult = Awaited<ReturnType<typeof parseSchemas>>;
11+
const cache = new Map<GameId, Promise<LoadResult>>();
1312

1413
export function loadGameSchemas(gameId: GameId) {
1514
const cached = cache.get(gameId);

src/data/preload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ import type { SchemaMetadata } from "./schemas";
44
export type PreloadedSchema = { declarations: Declaration[]; metadata: SchemaMetadata };
55

66
export const preloadedData = new Map<string, PreloadedSchema>();
7+
export const preloadErrors = new Map<string, string>();

src/entry.client.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
import { preloadedData } from "./data/preload";
1+
import { preloadedData, preloadErrors } from "./data/preload";
22
import { GAME_LIST } from "./games-list";
33

44
async function hydrate() {
55
const { loadGameSchemas } = await import("./data/loader");
66
await Promise.all(
77
GAME_LIST.map(async (g) => {
8-
const data = await loadGameSchemas(g.id);
9-
preloadedData.set(g.id, data);
8+
try {
9+
const data = await loadGameSchemas(g.id);
10+
preloadedData.set(g.id, data);
11+
} catch (e) {
12+
preloadErrors.set(g.id, e instanceof Error ? e.message : String(e));
13+
}
1014
}),
1115
);
1216

0 commit comments

Comments
 (0)