split up components more

This commit is contained in:
Brian Beck 2026-03-15 10:26:24 -07:00
parent d336c20098
commit 539dbba677
71 changed files with 1064 additions and 990 deletions

View file

@ -0,0 +1,95 @@
import { lazy, memo, Suspense } from "react";
import { type RootState } from "@react-three/fiber";
import { useDataSource } from "../state/gameEntityStore";
import { useRecording } from "./RecordingProvider";
import { AudioProvider } from "./AudioContext";
import { CamerasProvider } from "./CamerasProvider";
import { InputProducers } from "./InputHandlers";
import { SceneLighting } from "./SceneLighting";
import { ThreeCanvas } from "./ThreeCanvas";
import { TickProvider } from "./TickProvider";
import { EntityScene } from "./EntityScene";
import { ObserverCamera } from "./ObserverCamera";
import { AudioEnabled } from "./AudioEnabled";
import { DebugEnabled } from "./DebugEnabled";
import { InputConsumer } from "./InputConsumer";
function createLazy(
name: string,
loader: () => Promise<{
[name]: React.ComponentType<any>;
}>,
) {
return lazy(() => loader().then((mod) => ({ default: mod[name] })));
}
const StreamingController = createLazy(
"StreamingController",
() => import("@/src/components/StreamingController"),
);
const DebugElements = createLazy(
"DebugElements",
() => import("@/src/components/DebugElements"),
);
const Mission = createLazy("Mission", () => import("@/src/components/Mission"));
const ChatSoundPlayer = createLazy(
"ChatSoundPlayer",
() => import("@/src/components/ChatSoundPlayer"),
);
export const GameView = memo(function GameView({
dpr,
onCreated,
missionName,
missionType,
onLoadingChange,
}: {
dpr?: number;
onCreated?: (state: RootState) => void;
missionName: string;
missionType?: string;
onLoadingChange?: (isLoading: boolean, progress?: number) => void;
}) {
const recording = useRecording();
const dataSource = useDataSource();
const hasStreamData = dataSource === "demo" || dataSource === "live";
return (
<ThreeCanvas dpr={dpr} onCreated={onCreated}>
<TickProvider>
<CamerasProvider>
<InputProducers />
<AudioProvider>
<SceneLighting />
<Suspense>
<EntityScene />
</Suspense>
<ObserverCamera />
<AudioEnabled>
<ChatSoundPlayer />
</AudioEnabled>
<DebugEnabled>
<DebugElements />
</DebugEnabled>
{recording ? (
<Suspense>
<StreamingController recording={recording} />
</Suspense>
) : null}
{!hasStreamData ? (
<Suspense>
<Mission
key={`${missionName}~${missionType}`}
name={missionName}
missionType={missionType}
onLoadingChange={onLoadingChange}
/>
</Suspense>
) : null}
<InputConsumer />
</AudioProvider>
</CamerasProvider>
</TickProvider>
</ThreeCanvas>
);
});

View file

@ -1,4 +1,4 @@
import { useEffect, useState, useRef, RefObject } from "react";
import { useEffect, useState, useRef, RefObject, memo } from "react";
import { RiLandscapeFill } from "react-icons/ri";
import { FaRotateRight } from "react-icons/fa6";
import { LuClipboardList, LuUsers } from "react-icons/lu";
@ -22,7 +22,7 @@ import { hasMission } from "../manifest";
const DEFAULT_PANELS = ["controls", "preferences", "audio"];
export function InspectorControls({
export const InspectorControls = memo(function InspectorControls({
missionName,
missionType,
onOpenMapInfo,
@ -422,4 +422,4 @@ export function InspectorControls({
</div>
</div>
);
}
});

View file

@ -11,22 +11,17 @@ import {
ReactNode,
// ViewTransition,
} from "react";
import { Camera } from "three";
import { type Camera } from "three";
import { type RootState } from "@react-three/fiber";
import { type InvalidateFunction } from "@/src/components/ThreeCanvas";
import { InspectorControls } from "@/src/components/InspectorControls";
import { MissionSelect } from "@/src/components/MissionSelect";
import { StreamingMissionInfo } from "@/src/components/StreamingMissionInfo";
import { useSettings } from "@/src/components/SettingsProvider";
import { ObserverCamera } from "@/src/components/ObserverCamera";
import { AudioProvider } from "@/src/components/AudioContext";
import { CamerasProvider } from "@/src/components/CamerasProvider";
import { InputConsumer } from "./InputConsumer";
import {
RecordingProvider,
useRecording,
} from "@/src/components/RecordingProvider";
import { EntityScene } from "@/src/components/EntityScene";
import { TickProvider } from "@/src/components/TickProvider";
import { SceneLighting } from "@/src/components/SceneLighting";
import { useFeatures } from "@/src/components/FeaturesProvider";
import {
liveConnectionStore,
@ -37,12 +32,9 @@ import {
CurrentMission,
useMissionQueryState,
} from "@/src/components/useQueryParams";
import { ThreeCanvas, InvalidateFunction } from "@/src/components/ThreeCanvas";
import { InputProducers, InputProvider } from "./InputHandlers";
import { InputProvider } from "./InputHandlers";
import { VisualInput } from "./VisualInput";
import { LoadingIndicator } from "./LoadingIndicator";
import { AudioEnabled } from "./AudioEnabled";
import { DebugEnabled } from "./DebugEnabled";
import { engineStore } from "../state/engineStore";
import {
gameEntityStore,
@ -57,8 +49,8 @@ import {
LuPanelTopClose,
LuPanelTopOpen,
} from "react-icons/lu";
import styles from "./MapInspector.module.css";
import { useTouchDevice } from "./useTouchDevice";
import styles from "./MapInspector.module.css";
function ViewTransition({ children }: { children: ReactNode }) {
return children;
@ -73,23 +65,14 @@ function createLazy(
return lazy(() => loader().then((mod) => ({ default: mod[name] })));
}
const StreamingController = createLazy(
"StreamingController",
() => import("@/src/components/StreamingController"),
const GameView = createLazy(
"GameView",
() => import("@/src/components/GameView"),
);
const DemoPlaybackControls = createLazy(
"DemoPlaybackControls",
() => import("@/src/components/DemoPlaybackControls"),
);
const DebugElements = createLazy(
"DebugElements",
() => import("@/src/components/DebugElements"),
);
const Mission = createLazy("Mission", () => import("@/src/components/Mission"));
const ChatSoundPlayer = createLazy(
"ChatSoundPlayer",
() => import("@/src/components/ChatSoundPlayer"),
);
const PlayerHUD = createLazy(
"PlayerHUD",
() => import("@/src/components/PlayerHUD"),
@ -148,6 +131,7 @@ export function MapInspector() {
// Sync the mission query param when streaming data provides a mission name.
const streamMissionName = useMissionName();
const streamMissionType = useMissionType();
useEffect(() => {
if (!hasStreamData || !streamMissionName) return;
try {
@ -210,6 +194,30 @@ export function MapInspector() {
const cameraRef = useRef<Camera | null>(null);
const invalidateRef = useRef<InvalidateFunction | null>(null);
const handleOpenMapInfo = useCallback(() => setMapInfoOpen(true), []);
const handleOpenScoreScreen = useCallback(() => {
if (hasStreamData) {
setScoreScreenOpen(true);
}
}, [hasStreamData]);
const handleOpenServerBrowser = useCallback(() => {
if (features.live) {
setServerBrowserOpen(true);
}
}, [features.live]);
const handleChooseMap = useCallback(() => {
if (hasStreamData) {
setChoosingMap(true);
}
}, [hasStreamData]);
const handleCancelChoosingMap = useCallback(() => {
setChoosingMap(false);
}, []);
const handleCanvasCreated = useCallback((state: RootState) => {
cameraRef.current = state.camera;
invalidateRef.current = state.invalidate;
}, []);
return (
<main className={styles.Frame}>
<RecordingProvider>
@ -279,26 +287,14 @@ export function MapInspector() {
<InspectorControls
missionName={missionName}
missionType={missionType}
onOpenMapInfo={() => setMapInfoOpen(true)}
onOpenScoreScreen={
hasStreamData ? () => setScoreScreenOpen(true) : undefined
}
onOpenServerBrowser={
features.live ? () => setServerBrowserOpen(true) : undefined
}
onChooseMap={
hasStreamData
? () => {
setChoosingMap(true);
}
: undefined
}
onCancelChoosingMap={() => {
setChoosingMap(false);
}}
choosingMap={choosingMap}
cameraRef={cameraRef}
invalidateRef={invalidateRef}
onOpenMapInfo={handleOpenMapInfo}
onOpenScoreScreen={handleOpenScoreScreen}
onOpenServerBrowser={handleOpenServerBrowser}
onChooseMap={handleChooseMap}
onCancelChoosingMap={handleCancelChoosingMap}
/>
</div>
</ViewTransition>
@ -306,52 +302,19 @@ export function MapInspector() {
<InputProvider>
<div className={styles.Content}>
<div className={styles.ThreeView}>
<ThreeCanvas
dpr={
mapInfoOpen || serverBrowserOpen || scoreScreenOpen
? 0.25
: undefined
}
onCreated={(state) => {
cameraRef.current = state.camera;
invalidateRef.current = state.invalidate;
}}
>
<TickProvider>
<CamerasProvider>
<InputProducers />
<AudioProvider>
<SceneLighting />
<Suspense>
<EntityScene />
</Suspense>
<ObserverCamera />
<AudioEnabled>
<ChatSoundPlayer />
</AudioEnabled>
<DebugEnabled>
<DebugElements />
</DebugEnabled>
{recording ? (
<Suspense>
<StreamingController recording={recording} />
</Suspense>
) : null}
{!hasStreamData ? (
<Suspense>
<Mission
key={`${missionName}~${missionType}`}
name={missionName}
missionType={missionType}
onLoadingChange={handleLoadingChange}
/>
</Suspense>
) : null}
<InputConsumer />
</AudioProvider>
</CamerasProvider>
</TickProvider>
</ThreeCanvas>
<Suspense>
<GameView
missionName={missionName}
missionType={missionType}
dpr={
mapInfoOpen || serverBrowserOpen || scoreScreenOpen
? 0.25
: undefined
}
onCreated={handleCanvasCreated}
onLoadingChange={handleLoadingChange}
/>
</Suspense>
</div>
{hasStreamData && !scoreScreenOpen ? (
<Suspense>

View file

@ -1,4 +1,4 @@
import { ReactNode } from "react";
import { ReactNode, Suspense } from "react";
import { Canvas, GLProps, RootState } from "@react-three/fiber";
import { NoToneMapping, PCFShadowMap, SRGBColorSpace } from "three";
import { useDebug } from "./SettingsProvider";
@ -35,7 +35,7 @@ export function ThreeCanvas({
shadows={{ type: PCFShadowMap }}
onCreated={onCreated}
>
{children}
<Suspense>{children}</Suspense>
</Canvas>
);
}