Skip to content

Commit

Permalink
add multiple loading states
Browse files Browse the repository at this point in the history
  • Loading branch information
aymericdelab committed Dec 19, 2024
1 parent f6b5866 commit 27aeadd
Show file tree
Hide file tree
Showing 13 changed files with 253 additions and 128 deletions.
2 changes: 2 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "./index.css";
import { Toaster } from "./ui/components/Toaster";
import { TransactionNotification } from "./ui/components/TxEmit";
import { WorldLoading } from "./ui/components/WorldLoading";
import { World } from "./ui/layouts/World";

function App({ backgroundImage }: { backgroundImage: string }) {
Expand All @@ -9,6 +10,7 @@ function App({ backgroundImage }: { backgroundImage: string }) {
<Toaster />
<TransactionNotification />
<World backgroundImage={backgroundImage} />
<WorldLoading />
</>
);
}
Expand Down
23 changes: 21 additions & 2 deletions client/src/dojo/debouncedQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,14 @@ const marketQueue = new RequestQueue();

// Debounced functions that add to queues
export const debouncedSyncPosition = debounce(
async <S extends Schema>(client: ToriiClient, components: Component<S, Metadata, undefined>[], entityID: string) => {
async <S extends Schema>(
client: ToriiClient,
components: Component<S, Metadata, undefined>[],
entityID: string,
onComplete?: () => void,
) => {
await positionQueue.add(() => syncPosition(client, components, entityID));
onComplete?.();
},
100,
{ leading: true }, // Add leading: true to execute immediately on first call
Expand All @@ -67,8 +73,10 @@ export const debouncedAddToSubscriptionTwoKey = debounce(
client: ToriiClient,
components: Component<S, Metadata, undefined>[],
entityID: string[],
onComplete?: () => void,
) => {
await subscriptionQueue.add(() => addToSubscriptionTwoKeyModelbyRealmEntityId(client, components, entityID));
onComplete?.();
},
250,
{ leading: true },
Expand All @@ -79,8 +87,10 @@ export const debouncedAddToSubscriptionOneKey = debounce(
client: ToriiClient,
components: Component<S, Metadata, undefined>[],
entityID: string[],
onComplete?: () => void,
) => {
await subscriptionQueue.add(() => addToSubscriptionOneKeyModelbyRealmEntityId(client, components, entityID));
onComplete?.();
},
250,
{ leading: true },
Expand All @@ -91,8 +101,10 @@ export const debounceAddResourceArrivals = debounce(
client: ToriiClient,
components: Component<S, Metadata, undefined>[],
entityID: number[],
onComplete?: () => void,
) => {
await subscriptionQueue.add(() => addArrivalsSubscription(client, components, entityID));
onComplete?.();
},
250,
{ leading: true },
Expand All @@ -104,16 +116,23 @@ export const debouncedAddToSubscription = debounce(
components: Component<S, Metadata, undefined>[],
entityID: string[],
position?: { x: number; y: number }[],
onComplete?: () => void,
) => {
await subscriptionQueue.add(() => addToSubscription(client, components, entityID, position));
onComplete?.();
},
250,
{ leading: true },
);

export const debouncedAddMarketSubscription = debounce(
async <S extends Schema>(client: ToriiClient, components: Component<S, Metadata, undefined>[]) => {
async <S extends Schema>(
client: ToriiClient,
components: Component<S, Metadata, undefined>[],
onComplete?: () => void,
) => {
await marketQueue.add(() => addMarketSubscription(client, components));
onComplete?.();
},
500,
{ leading: true },
Expand Down
59 changes: 36 additions & 23 deletions client/src/dojo/setup.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AppStore } from "@/hooks/store/useUIStore";
import {
BUILDING_CATEGORY_POPULATION_CONFIG_ID,
HYPERSTRUCTURE_CONFIG_ID,
Expand Down Expand Up @@ -71,10 +72,12 @@ export const syncEntitiesDebounced = async <S extends Schema>(
};
};

export async function setup({ ...config }: DojoConfig) {
export async function setup(config: DojoConfig & { state: AppStore }) {
const network = await setupNetwork(config);
const components = createClientComponents(network);
const systemCalls = createSystemCalls(network);
const setConfigLoading = config.state.setConfigLoading;
const setSingleKeyLoading = config.state.setSingleKeyLoading;

const configClauses: Clause[] = [
{
Expand Down Expand Up @@ -114,13 +117,37 @@ export async function setup({ ...config }: DojoConfig) {
},
];

await getEntities(
network.toriiClient,
{ Composite: { operator: "Or", clauses: configClauses } },
network.contractComponents as any,
);
setConfigLoading(true);
await Promise.all([
getEntities(
network.toriiClient,
{ Composite: { operator: "Or", clauses: configClauses } },
network.contractComponents as any,
).finally(() => {
setConfigLoading(false);
}),
getEntities(
network.toriiClient,
{
Keys: {
keys: [undefined, undefined],
pattern_matching: "FixedLen",
models: ["s0_eternum-CapacityConfigCategory", "s0_eternum-ResourceCost"],
},
},
network.contractComponents as any,
[],
[],
40_000,
false,
),
]).finally(() => {
setConfigLoading(false);
});

// fetch all existing entities from torii

setSingleKeyLoading(true);
await getEntities(
network.toriiClient,
{
Expand Down Expand Up @@ -151,23 +178,9 @@ export async function setup({ ...config }: DojoConfig) {
[],
40_000,
false,
);

await getEntities(
network.toriiClient,
{
Keys: {
keys: [undefined, undefined],
pattern_matching: "FixedLen",
models: ["s0_eternum-CapacityConfigCategory", "s0_eternum-ResourceCost"],
},
},
network.contractComponents as any,
[],
[],
40_000,
false,
);
).finally(() => {
setSingleKeyLoading(false);
});

const sync = await syncEntitiesDebounced(network.toriiClient, network.contractComponents as any, [], false);

Expand Down
4 changes: 3 additions & 1 deletion client/src/hooks/store/useUIStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ThreeStore, createThreeStoreSlice } from "./_threeStore";
import { BattleViewInfo } from "./types";
import { BlockchainStore, createBlockchainStore } from "./useBlockchainStore";
import { RealmStore, createRealmStoreSlice } from "./useRealmStore";
import { WorldStore, createWorldStoreSlice } from "./useWorldLoading";

type TooltipType = {
content: React.ReactNode;
Expand Down Expand Up @@ -70,7 +71,7 @@ interface UIStore {
setShowToS: (show: boolean) => void;
}

export type AppStore = UIStore & PopupsStore & ThreeStore & BuildModeStore & RealmStore & BlockchainStore;
export type AppStore = UIStore & PopupsStore & ThreeStore & BuildModeStore & RealmStore & BlockchainStore & WorldStore;

const useUIStore = create(
subscribeWithSelector<AppStore>((set, get) => ({
Expand Down Expand Up @@ -139,6 +140,7 @@ const useUIStore = create(
...createBuildModeStoreSlice(set),
...createRealmStoreSlice(set),
...createBlockchainStore(set),
...createWorldStoreSlice(set),
})),
);

Expand Down
55 changes: 40 additions & 15 deletions client/src/hooks/store/useWorldLoading.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,45 @@
// World loading state
import { create } from "zustand";

interface WorldState {
isWorldLoading: boolean;
export interface WorldStore {
isSelectedStructureLoading: boolean;
isMarketLoading: boolean;
isStructuresLoading: boolean;
setWorldLoading: (loading: boolean) => void;
isPlayerStructuresLoading: boolean;
isArrivalsLoading: boolean;
isMapLoading: boolean;
isBankLoading: boolean;
isWorldLoading: boolean;
isHyperstructureLoading: boolean;
isSingleKeyLoading: boolean;
isConfigLoading: boolean;
setSelectedStructureLoading: (loading: boolean) => void;
setMarketLoading: (loading: boolean) => void;
setStructuresLoading: (loading: boolean) => void;
setPlayerStructuresLoading: (loading: boolean) => void;
setArrivalsLoading: (loading: boolean) => void;
setMapLoading: (loading: boolean) => void;
setBankLoading: (loading: boolean) => void;
setWorldLoading: (loading: boolean) => void;
setHyperstructureLoading: (loading: boolean) => void;
setSingleKeyLoading: (loading: boolean) => void;
setConfigLoading: (loading: boolean) => void;
}

export const useWorldStore = create<WorldState>((set) => ({
isWorldLoading: true,
isMarketLoading: true,
isStructuresLoading: true,
setWorldLoading: (loading: boolean) => set({ isWorldLoading: loading }),
export const createWorldStoreSlice = (set: any) => ({
isSelectedStructureLoading: false,
isMarketLoading: false,
isPlayerStructuresLoading: false,
isArrivalsLoading: false,
isMapLoading: false,
isBankLoading: false,
isWorldLoading: false,
isHyperstructureLoading: false,
isSingleKeyLoading: false,
isConfigLoading: false,
setSelectedStructureLoading: (loading: boolean) => set({ isSelectedStructureLoading: loading }),
setMarketLoading: (loading: boolean) => set({ isMarketLoading: loading }),
setStructuresLoading: (loading: boolean) => set({ isStructuresLoading: loading }),
}));
setPlayerStructuresLoading: (loading: boolean) => set({ isPlayerStructuresLoading: loading }),
setArrivalsLoading: (loading: boolean) => set({ isArrivalsLoading: loading }),
setMapLoading: (loading: boolean) => set({ isMapLoading: loading }),
setBankLoading: (loading: boolean) => set({ isBankLoading: loading }),
setWorldLoading: (loading: boolean) => set({ isWorldLoading: loading }),
setHyperstructureLoading: (loading: boolean) => set({ isHyperstructureLoading: loading }),
setSingleKeyLoading: (loading: boolean) => set({ isSingleKeyLoading: loading }),
setConfigLoading: (loading: boolean) => set({ isConfigLoading: loading }),
});
5 changes: 4 additions & 1 deletion client/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import App from "./App";
import { setup } from "./dojo/setup";
import { DojoProvider } from "./hooks/context/DojoContext";
import { StarknetProvider } from "./hooks/context/starknet-provider";
import useUIStore from "./hooks/store/useUIStore";
import "./index.css";
import GameRenderer from "./three/GameRenderer";
import { PWAUpdatePopup } from "./ui/components/pwa-update-popup";
Expand Down Expand Up @@ -62,8 +63,10 @@ async function init() {

root.render(<LoadingScreen backgroundImage={backgroundImage} />);

const state = useUIStore.getState();

const setupStart = performance.now();
const setupResult = await setup(dojoConfig);
const setupResult = await setup({ state, ...dojoConfig });
const setupEnd = performance.now();
console.log("SetupEnd", setupEnd - setupStart);

Expand Down
4 changes: 3 additions & 1 deletion client/src/three/scenes/Worldmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,8 @@ export default class WorldmapScene extends HexagonScene {
this.fetchedChunks.add(chunkKey);
console.log(startCol, startRow, range);

this.state.setMapLoading(true);

try {
const promiseTiles = getEntities(
this.dojo.network.toriiClient,
Expand Down Expand Up @@ -789,7 +791,7 @@ export default class WorldmapScene extends HexagonScene {
false,
);
Promise.all([promiseTiles, promisePositions]).then(([tiles, positions]) => {
// Promise.all([promiseTiles]).then(([tiles]) => {
this.state.setMapLoading(false);
});
} catch (error) {
// If there's an error, remove the chunk from cached set so it can be retried
Expand Down
64 changes: 64 additions & 0 deletions client/src/ui/components/WorldLoading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import useUIStore from "@/hooks/store/useUIStore";

export const WorldLoading = () => {
const isSelectedStructureLoading = useUIStore((state) => state.isSelectedStructureLoading);
const isMarketLoading = useUIStore((state) => state.isMarketLoading);
const isPlayerStructuresLoading = useUIStore((state) => state.isPlayerStructuresLoading);
const isArrivalsLoading = useUIStore((state) => state.isArrivalsLoading);
const isMapLoading = useUIStore((state) => state.isMapLoading);
const isBankLoading = useUIStore((state) => state.isBankLoading);
const isWorldLoading = useUIStore((state) => state.isWorldLoading);
const isHyperstructureLoading = useUIStore((state) => state.isHyperstructureLoading);
const isSingleKeyLoading = useUIStore((state) => state.isSingleKeyLoading);
const isConfigLoading = useUIStore((state) => state.isConfigLoading);

const anyLoading =
isSelectedStructureLoading ||
isMarketLoading ||
isPlayerStructuresLoading ||
isArrivalsLoading ||
isMapLoading ||
isBankLoading ||
isWorldLoading ||
isHyperstructureLoading ||
isSingleKeyLoading ||
isConfigLoading;

console.log({ isArrivalsLoading });

const getLoadingItems = () => {
const items = [];
if (isSelectedStructureLoading) items.push("Structure");
if (isMarketLoading) items.push("Market");
if (isPlayerStructuresLoading) items.push("Player Structures");
if (isArrivalsLoading) items.push("Arrivals");
if (isMapLoading) items.push("Map");
if (isBankLoading) items.push("Bank");
if (isWorldLoading) items.push("World");
if (isHyperstructureLoading) items.push("Hyperstructure");
if (isSingleKeyLoading) items.push("Single Key");
if (isConfigLoading) items.push("Config");
return items.join(", ");
};

return (
<div
className={`
z-1000
fixed left-1/2 transform -translate-x-1/2
bg-black/80 p-2 rounded-lg
transition-all duration-200 ease-in-out
origin-bottom scale-75 md:scale-100
${anyLoading ? "bottom-0 opacity-100" : "translate-y-full opacity-0"}
`}
id="world-loading"
>
{anyLoading && (
<div className="flex flex-row items-center justify-center h-full p-2">
<img src="/images/eternumloader.png" className="w-10" />
<div className="ml-4">Loading: {getLoadingItems()}</div>
</div>
)}
</div>
);
};
3 changes: 1 addition & 2 deletions client/src/ui/components/trading/MarketModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { useSetMarket } from "@/hooks/helpers/useTrade";
import useMarketStore from "@/hooks/store/useMarketStore";
import { useModalStore } from "@/hooks/store/useModalStore";
import useUIStore from "@/hooks/store/useUIStore";
import { useWorldStore } from "@/hooks/store/useWorldLoading";
import { BuildingThumbs } from "@/ui/config";
import CircleButton from "@/ui/elements/CircleButton";
import { LoadingAnimation } from "@/ui/elements/LoadingAnimation";
Expand Down Expand Up @@ -61,7 +60,7 @@ export const MarketModal = () => {
const bank = banks.length === 1 ? banks[0] : null;
const battles = useBattlesByPosition(bank?.position || { x: 0, y: 0 });

const isMarketLoading = useWorldStore((state) => state.isMarketLoading);
const isMarketLoading = useUIStore((state) => state.isMarketLoading);

const currentBlockTimestamp = useUIStore.getState().nextBlockTimestamp || 0;

Expand Down
Loading

0 comments on commit 27aeadd

Please sign in to comment.