enable HTTP caching for pet appearances

This commit is contained in:
Emi Matchu 2020-07-22 23:08:28 -07:00
parent 9f11c83b20
commit ffde7172de
2 changed files with 69 additions and 33 deletions

View file

@ -9,31 +9,31 @@ import { useQuery } from "@apollo/react-hooks";
export default function useOutfitAppearance(outfitState) { export default function useOutfitAppearance(outfitState) {
const { wornItemIds, speciesId, colorId, pose } = outfitState; const { wornItemIds, speciesId, colorId, pose } = outfitState;
const { loading, error, data } = useQuery( // We split this query out from the other one, so that we can HTTP cache it.
//
// While Apollo gives us fine-grained caching during the page session, we can
// only HTTP a full query at a time.
//
// This is a minor optimization with respect to keeping the user's cache
// populated with their favorite species/color combinations. Once we start
// caching the items by body instead of species/color, this could make color
// changes really snappy!
//
// The larger optimization is that this enables the CDN to edge-cache the
// most popular species/color combinations, for very fast previews on the
// HomePage. At time of writing, Vercel isn't actually edge-caching these, I
// assume because our traffic isn't enough - so let's keep an eye on this!
const { loading: loading1, error: error1, data: data1 } = useQuery(
gql` gql`
query OutfitAppearance( query OutfitPetAppearance($speciesId: ID!, $colorId: ID!, $pose: Pose!) {
$wornItemIds: [ID!]!
$speciesId: ID!
$colorId: ID!
$pose: Pose!
) {
petAppearance(speciesId: $speciesId, colorId: $colorId, pose: $pose) { petAppearance(speciesId: $speciesId, colorId: $colorId, pose: $pose) {
...PetAppearanceForOutfitPreview ...PetAppearanceForOutfitPreview
} }
items(ids: $wornItemIds) {
id
appearanceOn(speciesId: $speciesId, colorId: $colorId) {
...ItemAppearanceForOutfitPreview
}
}
} }
${itemAppearanceFragment}
${petAppearanceFragment} ${petAppearanceFragment}
`, `,
{ {
variables: { variables: {
wornItemIds,
speciesId, speciesId,
colorId, colorId,
pose, pose,
@ -42,16 +42,46 @@ export default function useOutfitAppearance(outfitState) {
} }
); );
const itemAppearances = React.useMemo( const { loading: loading2, error: error2, data: data2 } = useQuery(
() => (data?.items || []).map((i) => i.appearanceOn), gql`
[data] query OutfitItemsAppearance(
); $speciesId: ID!
const visibleLayers = React.useMemo( $colorId: ID!
() => getVisibleLayers(data?.petAppearance, itemAppearances), $wornItemIds: [ID!]!
[data, itemAppearances] ) {
items(ids: $wornItemIds) {
id
appearanceOn(speciesId: $speciesId, colorId: $colorId) {
...ItemAppearanceForOutfitPreview
}
}
}
${itemAppearanceFragment}
`,
{
variables: {
speciesId,
colorId,
wornItemIds,
},
skip: speciesId == null || colorId == null || wornItemIds.length === 0,
}
); );
return { loading, error, visibleLayers }; const itemAppearances = React.useMemo(
() => (data2?.items || []).map((i) => i.appearanceOn),
[data2]
);
const visibleLayers = React.useMemo(
() => getVisibleLayers(data1?.petAppearance, itemAppearances),
[data1, itemAppearances]
);
return {
loading: loading1 || loading2,
error: error1 || error2,
visibleLayers,
};
} }
export function getVisibleLayers(petAppearance, itemAppearances) { export function getVisibleLayers(petAppearance, itemAppearances) {

View file

@ -68,7 +68,8 @@ const typeDefs = gql`
appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance
} }
type PetAppearance { # Cache for 1 week (unlikely to change)
type PetAppearance @cacheControl(maxAge: 604800) {
id: ID! id: ID!
species: Species! species: Species!
color: Color! color: Color!
@ -84,7 +85,8 @@ const typeDefs = gql`
restrictedZones: [Zone!]! restrictedZones: [Zone!]!
} }
type AppearanceLayer { # Cache for 1 week (unlikely to change)
type AppearanceLayer @cacheControl(maxAge: 604800) {
id: ID! id: ID!
zone: Zone! zone: Zone!
imageUrl(size: LayerImageSize): String imageUrl(size: LayerImageSize): String
@ -98,7 +100,8 @@ const typeDefs = gql`
svgUrl: String svgUrl: String
} }
type Zone { # Cache for 1 week (unlikely to change)
type Zone @cacheControl(maxAge: 604800) {
id: ID! id: ID!
depth: Int! depth: Int!
label: String! label: String!
@ -109,12 +112,14 @@ const typeDefs = gql`
items: [Item!]! items: [Item!]!
} }
type Color { # Cache for 1 week (unlikely to change)
type Color @cacheControl(maxAge: 604800) {
id: ID! id: ID!
name: String! name: String!
} }
type Species { # Cache for 1 week (unlikely to change)
type Species @cacheControl(maxAge: 604800) {
id: ID! id: ID!
name: String! name: String!
} }
@ -138,8 +143,8 @@ const typeDefs = gql`
} }
type Query { type Query {
allColors: [Color!]! @cacheControl(maxAge: 10800) # Cache for 3 hours allColors: [Color!]! @cacheControl(maxAge: 10800) # Cache for 3 hours (we might add more!)
allSpecies: [Species!]! @cacheControl(maxAge: 10800) # Cache for 3 hours allSpecies: [Species!]! @cacheControl(maxAge: 10800) # Cache for 3 hours (we might add more!)
allValidSpeciesColorPairs: [SpeciesColorPair!]! # deprecated allValidSpeciesColorPairs: [SpeciesColorPair!]! # deprecated
items(ids: [ID!]!): [Item!]! items(ids: [ID!]!): [Item!]!
itemSearch(query: String!): ItemSearchResult! itemSearch(query: String!): ItemSearchResult!
@ -151,8 +156,9 @@ const typeDefs = gql`
limit: Int limit: Int
): ItemSearchResult! ): ItemSearchResult!
petAppearance(speciesId: ID!, colorId: ID!, pose: Pose!): PetAppearance petAppearance(speciesId: ID!, colorId: ID!, pose: Pose!): PetAppearance
@cacheControl(maxAge: 604800) # Cache for 1 week (unlikely to change)
petAppearances(speciesId: ID!, colorId: ID!): [PetAppearance!]! petAppearances(speciesId: ID!, colorId: ID!): [PetAppearance!]!
@cacheControl(maxAge: 10800) # Cache for 3 hours (we might add more!)
outfit(id: ID!): Outfit outfit(id: ID!): Outfit
petOnNeopetsDotCom(petName: String!): Outfit petOnNeopetsDotCom(petName: String!): Outfit
@ -371,7 +377,7 @@ const resolvers = {
return allPairs; return allPairs;
}, },
items: (_, { ids }) => { items: (_, { ids }) => {
return ids.map(id => ({ id })); return ids.map((id) => ({ id }));
}, },
itemSearch: async (_, { query }, { itemSearchLoader }) => { itemSearch: async (_, { query }, { itemSearchLoader }) => {
const items = await itemSearchLoader.load(query.trim()); const items = await itemSearchLoader.load(query.trim());