Cancel queued skin download if no more entities are using the skin

Also transfer skin download responbitilites if entity downloading a skin stops using that skin, but there is at least one other entity using that skin
This commit is contained in:
UnknownShadow200 2025-09-23 22:28:05 +10:00
parent 61beafa967
commit e39c5d55d3
2 changed files with 116 additions and 64 deletions

View File

@ -207,49 +207,63 @@ cc_bool Entity_TouchesAnyWater(struct Entity* e) {
/*########################################################################################################################* /*########################################################################################################################*
*------------------------------------------------------Entity skins-------------------------------------------------------* *------------------------------------------------------Entity skins-------------------------------------------------------*
*#########################################################################################################################*/ *#########################################################################################################################*/
static struct Entity* Entity_FirstOtherWithSameSkinAndFetchedSkin(struct Entity* except) {
struct Entity* e;
cc_string skin, eSkin;
int i;
skin = String_FromRawArray(except->SkinRaw);
for (i = 0; i < ENTITIES_MAX_COUNT; i++) {
if (!Entities.List[i] || Entities.List[i] == except) continue;
e = Entities.List[i];
eSkin = String_FromRawArray(e->SkinRaw);
if (e->SkinFetchState && String_Equals(&skin, &eSkin)) return e;
}
return NULL;
}
/* Copies skin data from another entity */ /* Copies skin data from another entity */
static void Entity_CopySkin(struct Entity* dst, struct Entity* src) { static void Entity_CopySkin(struct Entity* dst, struct Entity* src) {
dst->TextureId = src->TextureId; dst->TextureId = src->TextureId;
dst->SkinType = src->SkinType; dst->SkinType = src->SkinType;
dst->uScale = src->uScale; dst->uScale = src->uScale;
dst->vScale = src->vScale; dst->vScale = src->vScale;
dst->NonHumanSkin = src->NonHumanSkin;
} }
/* Resets skin data for the given entity */ /* Resets skin data for the given entity */
static void Entity_ResetSkin(struct Entity* e) { static void Entity_ResetSkin(struct Entity* e) {
e->uScale = 1.0f; e->vScale = 1.0f;
e->NonHumanSkin = false;
e->TextureId = 0; e->TextureId = 0;
e->SkinType = SKIN_64x32; e->uScale = 1.0f;
e->vScale = 1.0f;
}
static void CheckSkin_Unchecked(struct Entity* e) {
cc_string skin, eSkin;
struct Entity* other;
cc_uint8 flags;
cc_result res;
int i;
skin = String_FromRawArray(e->SkinRaw);
for (i = 0; i < ENTITIES_MAX_COUNT; i++)
{
other = Entities.List[i];
if (!other) continue;
/* Don't bother checking for other == e, as e->state is UNCHECKED anyways */
if (other->SkinFetchState < SKIN_FETCH_DOWNLOADING) continue;
eSkin = String_FromRawArray(other->SkinRaw);
if (!String_Equals(&skin, &eSkin)) continue;
/* Another entity with same skin either finished or is downloading */
if (other->SkinFetchState == SKIN_FETCH_COMPLETED) {
Entity_CopySkin(e, other);
e->SkinFetchState = SKIN_FETCH_COMPLETED;
} else {
e->SkinFetchState = SKIN_FETCH_WAITINGFOR;
}
return;
}
flags = e == &LocalPlayer_Instances[0].Base ? HTTP_FLAG_NOCACHE : 0;
e->_skinReqID = Http_AsyncGetSkin(&skin, flags);
e->SkinFetchState = SKIN_FETCH_DOWNLOADING;
} }
/* Copies or resets skin data for all entity with same skin */ /* Copies or resets skin data for all entity with same skin */
static void Entity_SetSkinAll(struct Entity* source, cc_bool reset) { static void Entity_SetSkinAll(struct Entity* source, cc_bool reset) {
struct Entity* e; struct Entity* e;
cc_string skin, eSkin; cc_string skin, eSkin;
skin = String_FromRawArray(source->SkinRaw);
int i; int i;
skin = String_FromRawArray(source->SkinRaw); for (i = 0; i < ENTITIES_MAX_COUNT; i++)
source->NonHumanSkin = Utils_IsUrlPrefix(&skin); {
for (i = 0; i < ENTITIES_MAX_COUNT; i++) {
if (!Entities.List[i]) continue; if (!Entities.List[i]) continue;
e = Entities.List[i]; e = Entities.List[i];
@ -327,7 +341,6 @@ static cc_result ApplySkin(struct Entity* e, struct Bitmap* bmp, struct Stream*
if ((res = Png_Decode(bmp, src))) return res; if ((res = Png_Decode(bmp, src))) return res;
Gfx_DeleteTexture(&e->TextureId); Gfx_DeleteTexture(&e->TextureId);
Entity_SetSkinAll(e, true);
if ((res = EnsurePow2Skin(e, bmp))) return res; if ((res = EnsurePow2Skin(e, bmp))) return res;
e->SkinType = Utils_CalcSkinType(bmp); e->SkinType = Utils_CalcSkinType(bmp);
@ -356,49 +369,81 @@ static void LogInvalidSkin(cc_result res, const cc_string* skin, const cc_uint8*
Logger_WarnFunc(&msg); Logger_WarnFunc(&msg);
} }
static void Entity_CheckSkin(struct Entity* e) { static void CheckSkin_Downloading(struct Entity* e) {
struct Entity* first;
struct HttpRequest item; struct HttpRequest item;
struct Stream mem; struct Stream mem;
struct Bitmap bmp; struct Bitmap bmp;
cc_string skin; cc_string skin;
cc_uint8 flags;
cc_result res; cc_result res;
/* Don't check skin if don't have to */ if (!Http_GetResult(e->_skinReqID, &item)) return;
if (!e->Model->usesSkin) return; Entity_SetSkinAll(e, true);
if (e->SkinFetchState == SKIN_FETCH_COMPLETED) return; if (!item.success) return;
Stream_ReadonlyMemory(&mem, item.data, item.size);
skin = String_FromRawArray(e->SkinRaw); skin = String_FromRawArray(e->SkinRaw);
if (!e->SkinFetchState) { if ((res = ApplySkin(e, &bmp, &mem, &skin))) {
first = Entity_FirstOtherWithSameSkinAndFetchedSkin(e); LogInvalidSkin(res, &skin, item.data, item.size);
flags = e == &LocalPlayer_Instances[0].Base ? HTTP_FLAG_NOCACHE : 0;
if (!first) {
e->_skinReqID = Http_AsyncGetSkin(&skin, flags);
e->SkinFetchState = SKIN_FETCH_DOWNLOADING;
} else {
Entity_CopySkin(e, first);
e->SkinFetchState = SKIN_FETCH_COMPLETED;
return;
}
} }
if (!Http_GetResult(e->_skinReqID, &item)) return; Mem_Free(bmp.scan0);
if (!item.success) {
Entity_SetSkinAll(e, true);
} else {
Stream_ReadonlyMemory(&mem, item.data, item.size);
if ((res = ApplySkin(e, &bmp, &mem, &skin))) {
LogInvalidSkin(res, &skin, item.data, item.size);
}
Mem_Free(bmp.scan0);
}
HttpRequest_Free(&item); HttpRequest_Free(&item);
} }
static void Entity_CheckSkin(struct Entity* e) {
/* Don't check skin if don't have to */
if (!e->Model->usesSkin) return;
switch (e->SkinFetchState)
{
case SKIN_FETCH_UNCHECKED:
CheckSkin_Unchecked(e); return;
case SKIN_FETCH_WAITINGFOR:
return; /* Waiting for another entity to download it */
case SKIN_FETCH_DOWNLOADING:
CheckSkin_Downloading(e); return;
case SKIN_FETCH_COMPLETED:
return; /* Nothing to do as skin has been downloaded */
}
}
/* Returns whether this entity is currently waiting on given skin to download */
static CC_INLINE cc_bool IsWaitingForSkinToDownload(struct Entity* e, cc_string* skin) {
cc_string eSkin;
if (e->SkinFetchState != SKIN_FETCH_WAITINGFOR) return false;
eSkin = String_FromRawArray(e->SkinRaw);
return String_Equals(skin, &eSkin);
}
/* Transfers skin downloading responsibility to another entity */
static void TransferSkinDownload(struct Entity* e, struct Entity* src) {
e->SkinFetchState = SKIN_FETCH_DOWNLOADING;
e->_skinReqID = src->_skinReqID;
}
/* Either transfers skin download or cancels it altogether */
static void DerefDownloadingSkin(struct Entity* src) {
struct Entity* e;
cc_string skin = String_FromRawArray(src->SkinRaw);
int i;
for (i = 0; i < ENTITIES_MAX_COUNT; i++)
{
if (!Entities.List[i]) continue;
e = Entities.List[i];
if (!IsWaitingForSkinToDownload(e, &skin)) continue;
Platform_Log1("Transferring skin download: %s", &skin);
TransferSkinDownload(e, src);
return;
}
Platform_Log1("Cancelling skin download: %s", &skin);
Http_TryCancel(src->_skinReqID);
}
/* Returns true if no other entities are sharing this skin texture */ /* Returns true if no other entities are sharing this skin texture */
static cc_bool CanDeleteTexture(struct Entity* except) { static cc_bool CanDeleteTexture(struct Entity* except) {
int i; int i;
@ -414,9 +459,10 @@ static cc_bool CanDeleteTexture(struct Entity* except) {
CC_NOINLINE static void DeleteSkin(struct Entity* e) { CC_NOINLINE static void DeleteSkin(struct Entity* e) {
if (CanDeleteTexture(e)) Gfx_DeleteTexture(&e->TextureId); if (CanDeleteTexture(e)) Gfx_DeleteTexture(&e->TextureId);
if (e->SkinFetchState == SKIN_FETCH_DOWNLOADING) DerefDownloadingSkin(e);
Entity_ResetSkin(e); Entity_ResetSkin(e);
e->SkinFetchState = 0; e->SkinFetchState = SKIN_FETCH_UNCHECKED;
} }
void Entity_SetSkin(struct Entity* e, const cc_string* skin) { void Entity_SetSkin(struct Entity* e, const cc_string* skin) {
@ -425,9 +471,11 @@ void Entity_SetSkin(struct Entity* e, const cc_string* skin) {
if (Utils_IsUrlPrefix(skin)) { if (Utils_IsUrlPrefix(skin)) {
tmp = *skin; tmp = *skin;
e->NonHumanSkin = true;
} else { } else {
String_InitArray(tmp, tmpBuffer); String_InitArray(tmp, tmpBuffer);
String_AppendColorless(&tmp, skin); String_AppendColorless(&tmp, skin);
e->NonHumanSkin = false;
} }
String_CopyToRawArray(e->SkinRaw, &tmp); String_CopyToRawArray(e->SkinRaw, &tmp);
} }

View File

@ -85,10 +85,14 @@ struct EntityVTABLE {
cc_bool (*ShouldRenderName)(struct Entity* e); cc_bool (*ShouldRenderName)(struct Entity* e);
}; };
/* Skin is still being downloaded asynchronously */ /* Entity's skin has not been checked yet */
#define SKIN_FETCH_DOWNLOADING 1 #define SKIN_FETCH_UNCHECKED 0
/* Skin was downloaded or copied from another entity with the same skin. */ /* Waiting for another entity with the same skin to finish downloading it */
#define SKIN_FETCH_COMPLETED 2 #define SKIN_FETCH_WAITINGFOR 1
/* Entity's skin is currently being downloaded asynchronously */
#define SKIN_FETCH_DOWNLOADING 2
/* Entity's skin has been downloaded or copied from another entity with the same skin. */
#define SKIN_FETCH_COMPLETED 3
/* true to restrict model scale (needed for local player, giant model collisions are too costly) */ /* true to restrict model scale (needed for local player, giant model collisions are too costly) */
#define ENTITY_FLAG_MODEL_RESTRICTED_SCALE 0x01 #define ENTITY_FLAG_MODEL_RESTRICTED_SCALE 0x01