Skip to content

Commit

Permalink
Merge pull request #59 from cardano-scaling/best-region-selection
Browse files Browse the repository at this point in the history
Create hook to select best region by latency
  • Loading branch information
Quantumplation authored Nov 14, 2024
2 parents 6aabad5 + f425218 commit 39f797b
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 95 deletions.
13 changes: 7 additions & 6 deletions src/components/DoomCanvas/DoomCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import { EGameType, EmscriptenModule, NewGameResponse } from "../../types";
import { useAppContext } from "../../context/useAppContext";
import { HydraMultiplayer } from "../../utils/hydra-multiplayer";
import useKeys from "../../hooks/useKeys";
import { getArgs } from "../../utils/game";
import { getArgs, getBaseUrl } from "../../utils/game";
import { useMutation } from "@tanstack/react-query";
import { SERVER_URL } from "../../constants";
import Card from "../Card";
import { FaRegCircleCheck } from "react-icons/fa6";
import { MdContentCopy } from "react-icons/md";
Expand All @@ -16,17 +15,19 @@ const DoomCanvas: React.FC = () => {
const isEffectRan = useRef(false);
const {
gameData: { code, petName, type },
region,
} = useAppContext();
const keys = useKeys();
const urlClipboard = useClipboard({ copiedTimeout: 1500 });
const baseUrl = getBaseUrl(region);

const { mutate: fetchGameData, data } = useMutation<NewGameResponse>({
mutationKey: ["fetchGameData", keys.address, code, type],
mutationFn: async () => {
const url =
type === EGameType.HOST
? `${SERVER_URL}new_game?address=${keys.address}`
: `${SERVER_URL}add_player?address=${keys.address}&id=${code}`;
? `${baseUrl}/new_game?address=${keys.address}`
: `${baseUrl}/add_player?address=${keys.address}&id=${code}`;
const response = await fetch(url);
return response.json();
},
Expand All @@ -42,10 +43,10 @@ const DoomCanvas: React.FC = () => {
);

useEffect(() => {
if (!keys.address) return;
if (!keys.address || !region) return;

fetchGameData();
}, [fetchGameData, keys.address]);
}, [fetchGameData, keys.address, region]);

useEffect(() => {
if (!keys.address || !data?.ip) return;
Expand Down
8 changes: 0 additions & 8 deletions src/components/InitialView/InitialView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { FC, useEffect, useState } from "react";
import Button from "../Button";
import hydraText from "../../assets/images/hydra-text.png";
import Modal from "../Modal";
import SelectContinentDialog from "../SelectContinentDialog";
import Layout from "../Layout";
import SetNameModal from "../SetNameModal";
import { useAppContext } from "../../context/useAppContext";
Expand All @@ -25,8 +24,6 @@ const InitialView: FC<InitialViewProps> = ({ startGame }) => {
const [isNameModalOpen, setIsNameModalOpen] = useState(
pathSegments[0] === EGameType.JOIN,
);
const [isSelectContinentModalOpen, setIsSelectContinentModalOpen] =
useState(false);
const code = pathSegments[1];

useEffect(() => {
Expand Down Expand Up @@ -111,11 +108,6 @@ const InitialView: FC<InitialViewProps> = ({ startGame }) => {
</p>
</div>
</Modal>
<SelectContinentDialog
close={() => setIsSelectContinentModalOpen(false)}
isOpen={isSelectContinentModalOpen}
startGame={startGame}
/>
<SetNameModal
close={() => setIsNameModalOpen(false)}
isOpen={isNameModalOpen}
Expand Down
53 changes: 0 additions & 53 deletions src/components/SelectContinentDialog/SelectContinentDialog.tsx

This file was deleted.

1 change: 0 additions & 1 deletion src/components/SelectContinentDialog/index.ts

This file was deleted.

11 changes: 1 addition & 10 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,5 @@ export const HANDLE_CACHE_KEY = "player-handle-cache";
export const HYDRA_DOOM_SESSION_KEY = "hydra-doom-session-key";
export const MAX_SPEED = 40;
export const REGION = import.meta.env.VITE_REGION;
export const REGIONS = [
{ name: "Ohio, NA", value: "us-east-2" },
{ name: "Oregon, NA", value: "us-west-2" },
{ name: "Frankfurt, Europe", value: "eu-central-1" },
{ name: "Cape Town, Africa", value: "af-south-1" },
{ name: "Melbourne, Australia", value: "ap-southeast-4" },
{ name: "Seoul, Asia", value: "ap-northeast-2" },
{ name: "Sao Paulo, SA", value: "sa-east-1" },
];
export const SERVER_URL = import.meta.env.VITE_SERVER_URL;
export const REGIONS = ["us-east-1"];
export const TIC_RATE_MAGIC = 35; // 35 is the ticrate in DOOM WASM they use to calculate time.
11 changes: 5 additions & 6 deletions src/context/AppContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import { FC, PropsWithChildren, useMemo, useState } from "react";
import { AppContext } from "./useAppContext";
import { EGameType } from "../types";
import useBestRegion from "../hooks/useBestRegion";
import { REGIONS } from "../constants";
import { EGameType, Region } from "../types";

const AppContextProvider: FC<PropsWithChildren> = ({ children }) => {
const { bestRegion } = useBestRegion(REGIONS);
const [gameData, setGameData] = useState({
code: "",
petName: "",
type: EGameType.SOLO,
});

const [region, setRegion] = useState<Region>(REGIONS[0]);

const value = useMemo(
() => ({
gameData,
region,
region: bestRegion,
setGameData,
setRegion,
}),
[gameData, region],
[gameData, bestRegion],
);

return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
Expand Down
9 changes: 3 additions & 6 deletions src/context/useAppContext.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { createContext, Dispatch, useContext } from "react";
import { EGameType, GameData, Region } from "../types";
import { REGIONS } from "../constants";
import { EGameType, GameData } from "../types";

interface AppContextInterface {
gameData: GameData;
region: Region;
region: string | null;
setGameData: Dispatch<React.SetStateAction<GameData>>;
setRegion: Dispatch<React.SetStateAction<Region>>;
}

export const AppContext = createContext<AppContextInterface>({
gameData: { petName: "", code: "", type: EGameType.SOLO },
region: REGIONS[0],
region: null,
setGameData: () => {},
setRegion: () => {},
});

export const useAppContext = () => {
Expand Down
68 changes: 68 additions & 0 deletions src/hooks/useBestRegion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useState, useEffect } from "react";
import { useQueries } from "@tanstack/react-query";

interface ServerHealth {
region: string;
latency: number;
error?: string;
}

const useBestRegion = (regions: string[]) => {
const [bestRegion, setBestRegion] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [isError, setIsError] = useState<boolean>(false);

const checkServerHealth = async (region: string): Promise<ServerHealth> => {
const url = `http://api.${region}.hydra-doom.sundae.fi/health`;

try {
const start = performance.now();
const response = await fetch(url, { method: "HEAD" });
const end = performance.now();

if (!response.ok) throw new Error("Server not healthy");

return {
region,
latency: end - start,
};
} catch (error: any) {
return {
region,
latency: Infinity,
error: error.message,
};
}
};

const results = useQueries({
queries: regions.map((region) => ({
queryKey: ["serverHealth", region],
queryFn: () => checkServerHealth(region),
staleTime: Infinity,
cacheTime: 0,
})),
});

useEffect(() => {
if (results.every((result) => result.isSuccess || result.isError)) {
const successfulResults = results
.filter((result) => result.isSuccess && result.data)
.map((result) => result.data as ServerHealth);

if (successfulResults.length > 0) {
const sortedByLatency = successfulResults.sort(
(a, b) => a.latency - b.latency,
);
setBestRegion(sortedByLatency[0].region);
} else {
setIsError(true);
}
setIsLoading(false);
}
}, [results]);

return { bestRegion, isLoading, isError };
};

export default useBestRegion;
5 changes: 0 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@ export interface NewGameResponse {
player_state: string;
}

export interface Region {
name: string;
value: string;
}

interface FileSystem {
createPreloadedFile(
parent: string,
Expand Down
5 changes: 5 additions & 0 deletions src/utils/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ export const getArgs = ({ type, code, petName }: GameData) => {

return args;
};

export const getBaseUrl = (region: string | null, local = false) => {
if (local) return "http://localhost:3000";
return `http://api.${region}.hydra-doom.sundae.fi`;
};

0 comments on commit 39f797b

Please sign in to comment.