major A_ChangeModel cleanup

This commit is contained in:
Ricardo Luís Vaz Silva 2023-04-20 22:06:55 -03:00 committed by Rachael Alexanderson
parent 5b85557ddb
commit b55ffdbfd3
4 changed files with 229 additions and 136 deletions

View file

@ -691,6 +691,7 @@ public:
DActorModelData() = default;
virtual void Serialize(FSerializer& arc) override;
virtual void OnDestroy() override;
};
class DViewPosition : public DObject

View file

@ -5107,7 +5107,196 @@ enum ChangeModelFlags
CMDL_USESURFACESKIN = 1 << 2,
};
DEFINE_ACTION_FUNCTION(AActor, A_ChangeModel)
void ChangeModelNative(
AActor * self,
AActor * invoker,
FStateParamInfo * stateinfo,
FName modeldef,
int i_modelindex,
const FString &p_modelpath,
FName model,
int i_skinindex,
const FString &p_skinpath,
FName skin,
int flags,
int generatorindex,
int i_animationindex,
const FString &p_animationpath,
FName animation
) {
if(!self) ThrowAbortException(X_READ_NIL, "In function parameter self");
if (modeldef != NAME_None && PClass::FindClass(modeldef.GetChars()) == nullptr)
{
Printf("Attempt to pass invalid modeldef name %s in %s.", modeldef.GetChars(), self->GetCharacterName());
return;
}
unsigned modelindex = i_modelindex < 0 ? 0 : i_modelindex;
unsigned skinindex = i_skinindex < 0 ? 0 : i_skinindex;
unsigned animationindex = i_animationindex < 0 ? 0 : i_animationindex;
AActor* mobj = (ACTION_CALL_FROM_PSPRITE() && (flags & CMDL_WEAPONTOPLAYER)) || ACTION_CALL_FROM_INVENTORY() ? self : invoker;
FString modelpath = p_modelpath;
FString skinpath = p_skinpath;
FString animationpath = p_animationpath;
if (modelpath.Len() != 0 && modelpath[(int)modelpath.Len() - 1] != '/') modelpath += '/';
if (skinpath.Len() != 0 && skinpath[(int)skinpath.Len() - 1] != '/') skinpath += '/';
if (animationpath.Len() != 0 && animationpath[(int)animationpath.Len() - 1] != '/') animationpath += '/';
if (mobj->modelData == nullptr)
{
auto ptr = Create<DActorModelData>();
ptr->hasModel = mobj->hasmodel;
ptr->modelDef = NAME_None;
mobj->modelData = ptr;
mobj->hasmodel = true;
GC::WriteBarrier(mobj, ptr);
};
unsigned skinPosition = skinindex + modelindex * MD3_MAX_SURFACES;
int queryModel = !(flags & CMDL_HIDEMODEL) ? model != NAME_None ? FindModel(modelpath.GetChars(), model.GetChars()) : -1 : -2;
int queryAnimation = animation != NAME_None ? FindModel(animationpath.GetChars(), animation.GetChars()) : -1;
mobj->modelData->modelDef = modeldef;
assert(mobj->modelData->modelIDs.Size() == mobj->modelData->modelFrameGenerators.Size());
if(mobj->modelData->modelIDs.Size() < modelindex)
{
mobj->modelData->modelIDs.AppendFill(-1, modelindex - mobj->modelData->modelIDs.Size());
mobj->modelData->modelFrameGenerators.AppendFill(-1, modelindex - mobj->modelData->modelFrameGenerators.Size());
}
if(mobj->modelData->animationIDs.Size() < animationindex)
{
mobj->modelData->animationIDs.AppendFill(-1, animationindex - mobj->modelData->animationIDs.Size());
}
if(flags & CMDL_USESURFACESKIN)
{
if(mobj->modelData->surfaceSkinIDs.Size() < skinPosition)
{
mobj->modelData->surfaceSkinIDs.AppendFill(FNullTextureID(), skinPosition - mobj->modelData->surfaceSkinIDs.Size());
}
}
else if(mobj->modelData->skinIDs.Size() < skinindex)
{
mobj->modelData->skinIDs.AppendFill(FNullTextureID(), skinindex - mobj->modelData->skinIDs.Size());
}
if(mobj->modelData->modelIDs.Size() == modelindex)
{
mobj->modelData->modelIDs.Push(queryModel);
mobj->modelData->modelFrameGenerators.Push(generatorindex);
}
else
{
mobj->modelData->modelIDs[modelindex] = queryModel;
mobj->modelData->modelFrameGenerators[modelindex] = generatorindex;
}
if(mobj->modelData->animationIDs.Size() == animationindex)
{
mobj->modelData->animationIDs.Push(queryAnimation);
}
else
{
mobj->modelData->animationIDs[animationindex] = queryAnimation;
}
auto skindata = skin != NAME_None ? LoadSkin(skinpath.GetChars(), skin.GetChars()) : FNullTextureID();
if (flags & CMDL_USESURFACESKIN)
{
if(mobj->modelData->surfaceSkinIDs.Size() == skinPosition)
{
mobj->modelData->surfaceSkinIDs.Push(skindata);
}
else
{
mobj->modelData->surfaceSkinIDs[skinPosition] = skindata;
}
}
else
{
if(mobj->modelData->skinIDs.Size() == skinindex)
{
mobj->modelData->skinIDs.Push(skindata);
}
else
{
mobj->modelData->skinIDs[skinindex] = skindata;
}
}
//[SM] - We need to serialize file paths and model names so that they are pushed on loading save files. Likewise, let's not include models that were already parsed when initialized.
if (queryModel >= 0)
{
FString fullName;
fullName.Format("%s%s", modelpath.GetChars(), model.GetChars());
bool found = false;
for (auto &m : savedModelFiles)
{
if(m.CompareNoCase(fullName) == 0)
{
found = true;
break;
}
}
if(!found) for (auto &m : Models)
{
if (m->mFileName.CompareNoCase(fullName) == 0)
{
found = true;
break;
}
}
if(!found) savedModelFiles.Push(fullName);
}
//Same for animations
if (queryAnimation >= 0)
{
FString fullName;
fullName.Format("%s%s", animationpath.GetChars(), animation.GetChars());
bool found = false;
for (auto &m : savedModelFiles)
{
if(m.CompareNoCase(fullName) == 0)
{
found = true;
break;
}
}
if(!found) for (auto &m : Models)
{
if (m->mFileName.CompareNoCase(fullName) == 0)
{
found = true;
break;
}
}
if(!found) savedModelFiles.Push(fullName);
}
if (mobj->modelData->modelIDs.Size() == 0 && mobj->modelData->modelFrameGenerators.Size() == 0 && mobj->modelData->skinIDs.Size() == 0 && mobj->modelData->surfaceSkinIDs.Size() == 0 && mobj->modelData->animationIDs.Size() == 0 && modeldef == NAME_None)
{
mobj->hasmodel = mobj->modelData->hasModel;
mobj->modelData->Destroy();
mobj->modelData = nullptr;
}
return;
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_ChangeModel, ChangeModelNative)
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_NAME(modeldef);
@ -5122,138 +5311,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_ChangeModel)
PARAM_INT(animationindex);
PARAM_STRING_VAL(animationpath);
PARAM_NAME(animation);
if (self == nullptr)
ACTION_RETURN_BOOL(false);
else if (modeldef != NAME_None && PClass::FindClass(modeldef.GetChars()) == nullptr)
{
Printf("Attempt to pass invalid modeldef name %s in %s.", modeldef.GetChars(), self->GetCharacterName());
ACTION_RETURN_BOOL(false);
}
else if (modelindex < 0)
{
Printf("Attempt to pass invalid model index %d in %s, index must be non-negative.", modelindex, self->GetCharacterName());
ACTION_RETURN_BOOL(false);
}
else if (skinindex < 0)
{
Printf("Attempt to pass invalid skin index %d in %s, index must be non-negative.", skinindex, self->GetCharacterName());
ACTION_RETURN_BOOL(false);
}
else if (animationindex < 0)
{
Printf("Attempt to pass invalid animation index %d in %s, index must be non-negative.", animationindex, self->GetCharacterName());
ACTION_RETURN_BOOL(false);
}
AActor* mobj = (ACTION_CALL_FROM_PSPRITE() && (flags & CMDL_WEAPONTOPLAYER)) || ACTION_CALL_FROM_INVENTORY() ? self : stateowner;
if (modelpath[(int)modelpath.Len() - 1] != '/') modelpath += '/';
if (skinpath[(int)skinpath.Len() - 1] != '/') skinpath += '/';
if (animationpath[(int)animationpath.Len() - 1] != '/') animationpath += '/';
if (mobj->modelData == nullptr)
{
auto ptr = Create<DActorModelData>();
ptr->hasModel = mobj->hasmodel ? 1 : 0;
ptr->modelDef = NAME_None;
mobj->modelData = ptr;
mobj->hasmodel = 1;
GC::WriteBarrier(mobj, ptr);
}
int maxModels = mobj->modelData->modelIDs.Size();
int maxSkins = mobj->modelData->skinIDs.Size();
int maxSurfaceSkins = mobj->modelData->surfaceSkinIDs.Size();
int maxAnimations = mobj->modelData->animationIDs.Size();
int maxGenerators = mobj->modelData->modelFrameGenerators.Size();
int skinPosition = skinindex + modelindex * MD3_MAX_SURFACES;
int queryModel = !(flags & CMDL_HIDEMODEL) ? model != NAME_None ? FindModel(modelpath.GetChars(), model.GetChars()) : -1 : -2;
int queryAnimation = animation != NAME_None ? FindModel(animationpath.GetChars(), animation.GetChars()) : -1;
//[SM] - Let's clear out any potential entries at the specified indices
mobj->modelData->modelDef = modeldef;
if(maxModels > modelindex) mobj->modelData->modelIDs.Pop(mobj->modelData->modelIDs[modelindex]);
if(maxAnimations > animationindex) mobj->modelData->animationIDs.Pop(mobj->modelData->animationIDs[animationindex]);
if(maxGenerators > modelindex) mobj->modelData->modelFrameGenerators.Pop(mobj->modelData->modelFrameGenerators[modelindex]);
if (flags & CMDL_USESURFACESKIN)
{
if (maxSurfaceSkins > skinPosition)
mobj->modelData->surfaceSkinIDs.Delete(skinPosition); //[SM] - It seems the only way to make sure this does what it's told is from Delete, not Pop
}
else
{
if (maxSkins > skinindex)
mobj->modelData->skinIDs.Pop(mobj->modelData->skinIDs[skinindex]);
}
//[SM] - We need to fill up any holes this new index will make so that it doesn't leave behind any undefined behavior
while ((int)mobj->modelData->modelIDs.Size() < modelindex) mobj->modelData->modelIDs.Push(-1);
while ((int)mobj->modelData->modelFrameGenerators.Size() < modelindex) mobj->modelData->modelFrameGenerators.Push(-1);
while ((int)mobj->modelData->animationIDs.Size() < modelindex) mobj->modelData->animationIDs.Push(-1);
if (flags & CMDL_USESURFACESKIN)
while ((int)mobj->modelData->surfaceSkinIDs.Size() < skinPosition) mobj->modelData->surfaceSkinIDs.Push(FNullTextureID());
else
while ((int)mobj->modelData->skinIDs.Size() < skinindex) mobj->modelData->skinIDs.Push(FNullTextureID());
mobj->modelData->modelIDs.Insert(modelindex, queryModel);
mobj->modelData->modelFrameGenerators.Insert(modelindex, generatorindex);
mobj->modelData->animationIDs.Insert(animationindex, queryAnimation);
if (flags & CMDL_USESURFACESKIN)
mobj->modelData->surfaceSkinIDs.Insert(skinPosition, skin != NAME_None ? LoadSkin(skinpath.GetChars(), skin.GetChars()) : FNullTextureID());
else
mobj->modelData->skinIDs.Insert(skinindex, skin != NAME_None ? LoadSkin(skinpath.GetChars(), skin.GetChars()) : FNullTextureID());
//[SM] - We need to serialize file paths and model names so that they are pushed on loading save files. Likewise, let's not include models that were already parsed when initialized.
if (queryModel >= 0)
{
FString fullName;
fullName.Format("%s%s", modelpath.GetChars(), model.GetChars());
bool allowPush = true;
for (unsigned i = 0; i < savedModelFiles.Size(); i++) if (!savedModelFiles[i].CompareNoCase(fullName)) allowPush = false;
for (unsigned i = 0; i < Models.Size()-1; i++) if (!Models[i]->mFileName.CompareNoCase(fullName)) allowPush = false;
if(allowPush) savedModelFiles.Push(fullName);
}
//Same for animations
if (queryAnimation >= 0)
{
FString fullName;
fullName.Format("%s%s", animationpath.GetChars(), animation.GetChars());
bool allowPush = true;
for (unsigned i = 0; i < savedModelFiles.Size(); i++) if (!savedModelFiles[i].CompareNoCase(fullName)) allowPush = false;
for (unsigned i = 0; i < Models.Size() - 1; i++) if (!Models[i]->mFileName.CompareNoCase(fullName)) allowPush = false;
if (allowPush) savedModelFiles.Push(fullName);
}
//[SM] - if an indice of modelIDs or skinIDs comes up blank and it's the last one, just delete it. For using very large amounts of indices, common sense says to just not run this repeatedly.
while (mobj->modelData->modelIDs.Size() > 0 && mobj->modelData->modelIDs.Last() == -1)
mobj->modelData->modelIDs.Pop(mobj->modelData->modelIDs.Last());
while (mobj->modelData->modelFrameGenerators.Size() > 0 && mobj->modelData->modelFrameGenerators.Last() == -1)
mobj->modelData->modelFrameGenerators.Pop(mobj->modelData->modelFrameGenerators.Last());
while (mobj->modelData->skinIDs.Size() > 0 && mobj->modelData->skinIDs.Last() == FNullTextureID())
mobj->modelData->skinIDs.Pop(mobj->modelData->skinIDs.Last());
while (mobj->modelData->surfaceSkinIDs.Size() > 0 && mobj->modelData->surfaceSkinIDs.Last() == FNullTextureID())
mobj->modelData->surfaceSkinIDs.Pop(mobj->modelData->surfaceSkinIDs.Last());
while (mobj->modelData->animationIDs.Size() > 0 && mobj->modelData->animationIDs.Last() == -1)
mobj->modelData->animationIDs.Pop(mobj->modelData->animationIDs.Last());
if (mobj->modelData->modelIDs.Size() == 0 && mobj->modelData->modelFrameGenerators.Size() == 0 && mobj->modelData->skinIDs.Size() == 0 && mobj->modelData->surfaceSkinIDs.Size() == 0 && mobj->modelData->animationIDs.Size() == 0 && modeldef == NAME_None)
{
mobj->hasmodel = mobj->modelData->hasModel;
mobj->modelData->modelIDs.Reset();
mobj->modelData->modelFrameGenerators.Reset();
mobj->modelData->skinIDs.Reset();
mobj->modelData->surfaceSkinIDs.Reset();
mobj->modelData->animationIDs.Reset();
mobj->modelData->Destroy();
}
ChangeModelNative(self,stateowner,stateinfo,modeldef,modelindex,modelpath,model,skinindex,skinpath,skin,flags,generatorindex,animationindex,animationpath,animation);
return 0;
}

View file

@ -1336,6 +1336,15 @@ void DActorModelData::Serialize(FSerializer& arc)
("hasModel", hasModel);
}
void DActorModelData::OnDestroy()
{
modelIDs.Reset();
modelFrameGenerators.Reset();
skinIDs.Reset();
surfaceSkinIDs.Reset();
animationIDs.Reset();
}
//----------------------------------------------------------------------------
//
// PROC P_ExplodeMissile

View file

@ -344,16 +344,40 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr
if (actor->modelData->skinIDs[i].isValid())
skinid = actor->modelData->skinIDs[i];
}
for (int surface = i * MD3_MAX_SURFACES; surface < (i + 1) * MD3_MAX_SURFACES; surface++)
unsigned sz1 = smf->surfaceskinIDs.Size();
unsigned sz2 = actor->modelData->surfaceSkinIDs.Size();
unsigned end = (i + 1) * MD3_MAX_SURFACES;
if(actor->modelData->surfaceSkinIDs.Size() > 0)
{
if (surface < (int)actor->modelData->surfaceSkinIDs.Size())
long last_ok = -1;
surfaceskinids.Resize(actor->modelData->surfaceSkinIDs.Size());
for (unsigned surface = i * MD3_MAX_SURFACES; surface < end; surface++)
{
if (surface >= sz2) break;
if (actor->modelData->surfaceSkinIDs[surface].isValid())
{
// only make a copy of the surfaceskinIDs array if really needed
if (surfaceskinids.Size() == 0) surfaceskinids = smf->surfaceskinIDs;
surfaceskinids[surface] = actor->modelData->surfaceSkinIDs[surface];
last_ok = surface;
}
else if(surface < sz1)
{
surfaceskinids[surface] = smf->surfaceskinIDs[surface];
}
else
{
surfaceskinids[surface].SetInvalid();
}
}
if(last_ok >= 0)
{
surfaceskinids.Resize(max(last_ok + 1, (long)sz1)); // clear out
}
else
{
surfaceskinids.Clear();
}
}
}