From 20a0a5bf7ff5fc088d50582a253fa8de99f5c96a Mon Sep 17 00:00:00 2001 From: Nigel Breslaw Date: Sun, 21 Apr 2024 03:28:15 +0300 Subject: [PATCH] refactor: break up inventory data (#1351) Before the different pages were all grouped together. For example weaponsInventory was a single object in the state. This meant if one item changed all 4 pages were updated. The new system breaks the pages to each character (guardians and vault). It then does an eqaulity check to ensure only characters with changed data are updated. Previously transfers could cause the UI to update and take up to 1 second to redraw everything. It seems the largest updates are now 250ms. --- native/app/bungie/Types.ts | 5 +++- native/app/inventory/Common.ts | 12 ++++----- native/app/screens/ArmorPage.tsx | 5 ++-- native/app/screens/GeneralPage.tsx | 5 ++-- native/app/screens/InventoryPage.tsx | 22 ++++++++++++--- native/app/screens/WeaponsPage.tsx | 5 ++-- native/app/store/AccountInventoryLogic.ts | 33 ++++++++++++++++++++--- native/app/store/AccountLogic.ts | 6 +++++ native/app/store/AccountSlice.ts | 14 +--------- native/app/store/AuthenticationSlice.ts | 4 +-- 10 files changed, 71 insertions(+), 40 deletions(-) diff --git a/native/app/bungie/Types.ts b/native/app/bungie/Types.ts index ffe604f98..6562a3a79 100644 --- a/native/app/bungie/Types.ts +++ b/native/app/bungie/Types.ts @@ -5,7 +5,7 @@ import type { GuardianRaceType, ItemType, } from "@/app/bungie/Hashes.ts"; -import type { DamageType } from "@/app/inventory/Common.ts"; +import type { DamageType, UISections } from "@/app/inventory/Common.ts"; import { array, boolean, isoTimestamp, merge, number, object, optional, record, string, unknown } from "valibot"; import type { Output } from "valibot"; @@ -434,4 +434,7 @@ export type GGCharacterUiData = { secondarySpecial: string; lastActiveCharacter: boolean; ggCharacterType: GGCharacterType; + armorPageData: UISections[]; + generalPageData: UISections[]; + weaponsPageData: UISections[]; }; diff --git a/native/app/inventory/Common.ts b/native/app/inventory/Common.ts index 9ebc07d88..4abaf5788 100644 --- a/native/app/inventory/Common.ts +++ b/native/app/inventory/Common.ts @@ -3,7 +3,7 @@ export const basePath = "https://www.bungie.net/Platform"; export const iconUrl = "https://www.bungie.net/common/destiny2_content/icons/"; export const screenshotUrl = "https://www.bungie.net/common/destiny2_content/screenshots/"; -export enum InventoryPage { +export enum InventoryPageEnums { Unknown = 0, Weapons = 1, Armor = 2, @@ -153,18 +153,18 @@ export const equipSectionBuckets = [ SectionBuckets.Finisher, ]; -export function getInventoryPage(bucket: number): InventoryPage { +export function getInventoryPage(bucket: number): InventoryPageEnums { if (weaponsPageBuckets.includes(bucket)) { - return InventoryPage.Weapons; + return InventoryPageEnums.Weapons; } if (armorPageBuckets.includes(bucket)) { - return InventoryPage.Armor; + return InventoryPageEnums.Armor; } if (generalPageBuckets.includes(bucket)) { - return InventoryPage.General; + return InventoryPageEnums.General; } console.log("Unknown page", bucket); - return InventoryPage.Unknown; + return InventoryPageEnums.Unknown; } export enum DamageType { diff --git a/native/app/screens/ArmorPage.tsx b/native/app/screens/ArmorPage.tsx index 3727f352f..63ec906cb 100644 --- a/native/app/screens/ArmorPage.tsx +++ b/native/app/screens/ArmorPage.tsx @@ -1,7 +1,6 @@ +import { InventoryPageEnums } from "@/app/inventory/Common.ts"; import InventoryPage from "@/app/screens/InventoryPage.tsx"; -import { useGGStore } from "@/app/store/GGStore.ts"; export default function ArmorPage() { - const armorPageData = useGGStore((state) => state.armorPageData); - return ; + return ; } diff --git a/native/app/screens/GeneralPage.tsx b/native/app/screens/GeneralPage.tsx index d1addc8f4..924600aed 100644 --- a/native/app/screens/GeneralPage.tsx +++ b/native/app/screens/GeneralPage.tsx @@ -1,7 +1,6 @@ import InventoryPage from "@/app/screens/InventoryPage.tsx"; -import { useGGStore } from "@/app/store/GGStore.ts"; +import { InventoryPageEnums } from "@/app/inventory/Common.ts"; export default function GeneralPage() { - const generalPageData = useGGStore((state) => state.generalPageData); - return ; + return ; } diff --git a/native/app/screens/InventoryPage.tsx b/native/app/screens/InventoryPage.tsx index 95b3d2d0f..d29f86376 100644 --- a/native/app/screens/InventoryPage.tsx +++ b/native/app/screens/InventoryPage.tsx @@ -1,4 +1,5 @@ -import type { UISections } from "@/app/inventory/Common.ts"; +import type { GGCharacterUiData } from "@/app/bungie/Types.ts"; +import { InventoryPageEnums, type UISections } from "@/app/inventory/Common.ts"; import { UiCellRenderItem } from "@/app/inventory/UiRowRenderItem.tsx"; import { calcCurrentListIndex } from "@/app/screens/Helpers.ts"; import { useGGStore } from "@/app/store/GGStore.ts"; @@ -11,8 +12,19 @@ import { ScrollView } from "react-native-gesture-handler"; const pageEstimatedFlashListItemSize = [130, 130, 130, 200]; +function getData(ggCharacter: GGCharacterUiData, inventoryPage: InventoryPageEnums): UISections[] | undefined { + switch (inventoryPage) { + case InventoryPageEnums.Armor: + return ggCharacter.armorPageData; + case InventoryPageEnums.General: + return ggCharacter.generalPageData; + case InventoryPageEnums.Weapons: + return ggCharacter.weaponsPageData; + } +} + type InventoryPageProps = { - inventoryPageData: UISections[][]; + inventoryPages: InventoryPageEnums; }; const rootStyles = StyleSheet.create({ @@ -78,6 +90,8 @@ export default function InventoryPage(props: InventoryPageProps) { const debouncedMove = debounce(listMoved, 40); const debounceListIndex = debounce(calcCurrentListIndex, 40); + const mainData = useGGStore((state) => state.ggCharacters); + return ( debounceListIndex(e.nativeEvent.contentOffset.x, HOME_WIDTH)} ref={pagedScrollRef} > - {props.inventoryPageData.map((list, index) => { + {mainData.map((character, index) => { return ( // biome-ignore lint/suspicious/noArrayIndexKey: @@ -95,7 +109,7 @@ export default function InventoryPage(props: InventoryPageProps) { ref={(ref) => { listRefs.current[index] = ref; }} - data={list} + data={getData(character, props.inventoryPages)} renderItem={UiCellRenderItem} keyExtractor={keyExtractor} estimatedItemSize={pageEstimatedFlashListItemSize[index]} diff --git a/native/app/screens/WeaponsPage.tsx b/native/app/screens/WeaponsPage.tsx index 93bf8f1b0..42a77965e 100644 --- a/native/app/screens/WeaponsPage.tsx +++ b/native/app/screens/WeaponsPage.tsx @@ -1,7 +1,6 @@ +import { InventoryPageEnums } from "@/app/inventory/Common.ts"; import InventoryPage from "@/app/screens/InventoryPage.tsx"; -import { useGGStore } from "@/app/store/GGStore.ts"; export default function WeaponsPage() { - const weaponsPageData = useGGStore((state) => state.weaponsPageData); - return ; + return ; } diff --git a/native/app/store/AccountInventoryLogic.ts b/native/app/store/AccountInventoryLogic.ts index 4a9b1e807..d56f19a91 100644 --- a/native/app/store/AccountInventoryLogic.ts +++ b/native/app/store/AccountInventoryLogic.ts @@ -29,22 +29,47 @@ import { } from "@/app/utilities/Constants.ts"; import { typeAndPowerSort } from "@/app/utilities/Helpers.ts"; import { create } from "mutative"; +import { deepEqual } from "fast-equals"; // ------------------------------ // UI data creation // ------------------------------ export function updateAllPages(get: AccountSliceGetter, set: AccountSliceSetter) { - const p1 = performance.now(); createUIData(get); + const p1 = performance.now(); + const weaponsPageData = buildUIData(get, weaponsPageBuckets); + const ggCharacters = get().ggCharacters; + const armorPageData = buildUIData(get, armorPageBuckets); const generalPageData = buildUIData(get, generalPageBuckets); const p2 = performance.now(); - console.log("built UI data for all pages", `${(p2 - p1).toFixed(4)} ms`); - set({ weaponsPageData, armorPageData, generalPageData }); + console.log("buildUIData took:", `${(p2 - p1).toFixed(4)} ms`); + + const updatedGGCharacters = create(ggCharacters, (draft) => { + let index = 0; + for (const ggCharacter of draft) { + const newWeaponsPageData = weaponsPageData[index]; + + if (newWeaponsPageData && !deepEqual(ggCharacter.weaponsPageData, newWeaponsPageData)) { + ggCharacter.weaponsPageData = newWeaponsPageData; + } + const newArmorPageData = armorPageData[index]; + if (newArmorPageData && !deepEqual(ggCharacter.armorPageData, newArmorPageData)) { + ggCharacter.armorPageData = newArmorPageData; + } + + const newGeneralPageData = generalPageData[index]; + if (newGeneralPageData && !deepEqual(ggCharacter.generalPageData, newGeneralPageData)) { + ggCharacter.generalPageData = newGeneralPageData; + } + index++; + } + }); + set({ ggCharacters: updatedGGCharacters }); const p3 = performance.now(); - console.log("Rebuild UI took:", `${(p3 - p2).toFixed(4)} ms`); + console.log("rebuild UI took:", `${(p3 - p2).toFixed(4)} ms`); } function createUIData(get: AccountSliceGetter) { diff --git a/native/app/store/AccountLogic.ts b/native/app/store/AccountLogic.ts index 41bac9d47..884f7459a 100644 --- a/native/app/store/AccountLogic.ts +++ b/native/app/store/AccountLogic.ts @@ -46,6 +46,9 @@ export function getCharactersAndVault(guardians: Record): GGCh secondarySpecial, lastActiveCharacter: false, ggCharacterType: GGCharacterType.Vault, + armorPageData: [], + generalPageData: [], + weaponsPageData: [], }; ggCharacters.push(vaultData); @@ -68,6 +71,9 @@ function addCharacterDefinition(guardianData: GuardianData): GGCharacterUiData { secondarySpecial: "", lastActiveCharacter: false, ggCharacterType: GGCharacterType.Guardian, + armorPageData: [], + generalPageData: [], + weaponsPageData: [], }; return data; diff --git a/native/app/store/AccountSlice.ts b/native/app/store/AccountSlice.ts index d30889240..1def82e2f 100644 --- a/native/app/store/AccountSlice.ts +++ b/native/app/store/AccountSlice.ts @@ -10,13 +10,7 @@ import { type ProfileData, type VaultData, } from "@/app/bungie/Types.ts"; -import { - SectionBuckets, - characterBuckets, - iconUrl, - type DestinyItemIdentifier, - type UISections, -} from "@/app/inventory/Common.ts"; +import { SectionBuckets, characterBuckets, iconUrl, type DestinyItemIdentifier } from "@/app/inventory/Common.ts"; import { findDestinyItem, getCharactersAndVault } from "@/app/store/AccountLogic.ts"; import { bucketTypeHashArray, @@ -57,9 +51,6 @@ export interface AccountSlice { // dictates ggCharacters: GGCharacterUiData[]; - armorPageData: UISections[][]; - generalPageData: UISections[][]; - weaponsPageData: UISections[][]; selectedItem: DestinyItem | null; responseMintedTimestamp: Date; @@ -90,9 +81,6 @@ export const createAccountSlice: StateCreator = (s ggCharacters: [], - armorPageData: [], - generalPageData: [], - weaponsPageData: [], selectedItem: null, responseMintedTimestamp: new Date(1977), diff --git a/native/app/store/AuthenticationSlice.ts b/native/app/store/AuthenticationSlice.ts index 636fae61f..c5b824e7e 100644 --- a/native/app/store/AuthenticationSlice.ts +++ b/native/app/store/AuthenticationSlice.ts @@ -97,9 +97,7 @@ export const createAuthenticationSlice: StateCreator