Compare commits

...

2 commits

Author SHA1 Message Date
0e1b1eded3 Skip missing image layers in /api/outfitImage
Sometimes we don't have an image for an SWF asset. This can happen when
assets were converted to HTML5 without a PNG specified in the manifest.
(One example is bio/363, the Hind Biology for the Mutant Blumaroo.)

I'm noticing there's a second issue here on DTI's end, where it looks
like *we* have two copies of certain layers? I wonder if this was a bug
in like, the impress-2020 modeling code we tested at one point? Or if
this is in our main modeling code? e.g. the Mutant Blumaroo's bio/363
is in our database with DTI ID 192 and DTI ID 606955, and only the
former is registered as having an image (which we made ourselves and
host on S3).

So, I'm gonna start by just having the more graceful failure mode of
skipping the missing layer. But I'm also concerned about the root here,
I'll investigate!
2024-02-09 08:59:36 -08:00
8fb561338a Fix PetAppearance ID when there's an Alt Style
I don't think this actually affects any clients in practice, but now
that `PetAppearance` can be influenced not just by pet state ID but
also alt style ID, it's no longer correct to just use pet state ID as
the `id` (the default cache key). This could theoretically cause an alt
style to be locally cached as the default appearance of a
species/color/pose.

Instead, we now append an extra string in the case that there's also an
alt style influencing the appearance. This is a bit of a mess, because
I know there's some tooling that's expecting this field to be strictly
the pet state ID, mainly for support/debugging purposes I think? But I
imagine that's not a big deal in practice, and this change should
narrow the scope of a bug like that to just be like, "error: pet state
123-with-alt-style-456 not found" when trying to save a change to it
or something. Good enough!
2024-02-09 08:16:00 -08:00
2 changed files with 22 additions and 9 deletions

View file

@ -84,7 +84,7 @@ async function handle(req, res) {
return reject( return reject(
res, res,
`Error loading data for outfit ${outfitId}: ${e.message}`, `Error loading data for outfit ${outfitId}: ${e.message}`,
500 500,
); );
} }
} else if (req.query.id) { } else if (req.query.id) {
@ -101,7 +101,7 @@ async function handle(req, res) {
return reject( return reject(
res, res,
`Error loading data for outfit ${outfitId}: ${e.message}`, `Error loading data for outfit ${outfitId}: ${e.message}`,
500 500,
); );
} }
@ -188,7 +188,7 @@ async function loadLayerUrlsForSavedOutfit(outfitId, size) {
if (errors && errors.length > 0) { if (errors && errors.length > 0) {
throw new Error( throw new Error(
`GraphQL Error: ${errors.map((e) => e.message).join(", ")}` `GraphQL Error: ${errors.map((e) => e.message).join(", ")}`,
); );
} }
@ -197,17 +197,20 @@ async function loadLayerUrlsForSavedOutfit(outfitId, size) {
} }
const { petAppearance, itemAppearances } = data.outfit; const { petAppearance, itemAppearances } = data.outfit;
const visibleLayers = getVisibleLayers(petAppearance, itemAppearances);
const visibleLayers = getVisibleLayers(petAppearance, itemAppearances);
visibleLayers.sort((a, b) => a.depth - b.depth);
const imageUrls = [];
for (const layer of visibleLayers) { for (const layer of visibleLayers) {
if (!layer.imageUrl) { if (!layer.imageUrl) {
throw new Error(`layer ${layer.id} has no imageUrl for size ${size}`); console.warn(`layer ${layer.id} has no imageUrl for size ${size}`);
continue;
} }
imageUrls.push(layer.imageUrl);
} }
return visibleLayers return imageUrls;
.sort((a, b) => a.depth - b.depth)
.map((layer) => layer.imageUrl);
} }
async function loadUpdatedAtForSavedOutfit(outfitId) { async function loadUpdatedAtForSavedOutfit(outfitId) {
@ -230,7 +233,7 @@ function reject(res, message, status = 400) {
async function handleWithBeeline(req, res) { async function handleWithBeeline(req, res) {
beeline.withTrace( beeline.withTrace(
{ name: "api/outfitImage", operation_name: "api/outfitImage" }, { name: "api/outfitImage", operation_name: "api/outfitImage" },
() => handle(req, res) () => handle(req, res),
); );
} }

View file

@ -73,7 +73,11 @@ const typeDefs = gql`
} }
type PetAppearance @cacheControl(maxAge: ${oneHour}, staleWhileRevalidate: ${oneWeek}) { type PetAppearance @cacheControl(maxAge: ${oneHour}, staleWhileRevalidate: ${oneWeek}) {
"""
NOTE: In the case of an alt style, this won't match petStateId!
"""
id: ID! id: ID!
species: Species! species: Species!
color: Color! color: Color!
pose: Pose! pose: Pose!
@ -269,6 +273,12 @@ const resolvers = {
}, },
PetAppearance: { PetAppearance: {
id: ({ id, altStyleId }) => {
if (altStyleId != null) {
return `${id}-with-alt-style-${altStyleId}`;
}
return id;
},
color: async ({ id }, _, { petStateLoader, petTypeLoader }) => { color: async ({ id }, _, { petStateLoader, petTypeLoader }) => {
const petState = await petStateLoader.load(id); const petState = await petStateLoader.load(id);
const petType = await petTypeLoader.load(petState.petTypeId); const petType = await petTypeLoader.load(petState.petTypeId);