mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-25 05:21:02 +00:00
e7aa10b5c8
This was done to ensure that this code only runs when the thinker itself is fully set up. With a constructor there is no control about such things, if some common initialization needs to be done it has to be in the base constructor, but that makes the entire approach chosen here to ensure proper linking into the thinker chains impossible. ZDoom originally did it that way, which resulted in a very inflexible system and required some awful hacks to let the serializer work with it - the corresponding bSerialOverride flag is now gone. The only thinker class still having a constructor is DFraggleThinker, because it contains non-serializable data that needs to be initialized in a piece of code that always runs, regardless of whether the object is created explicitly or from a savegame.
1030 lines
29 KiB
C++
1030 lines
29 KiB
C++
/*
|
|
** dobjtype.cpp
|
|
** Implements the type information class
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 1998-2016 Randy Heit
|
|
** Copyright 2005-2016 Christoph Oelckers
|
|
** All rights reserved.
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
// HEADER FILES ------------------------------------------------------------
|
|
|
|
#include <limits>
|
|
|
|
#include "dobject.h"
|
|
#include "serializer.h"
|
|
#include "actor.h"
|
|
#include "autosegs.h"
|
|
#include "v_text.h"
|
|
#include "a_pickups.h"
|
|
#include "d_player.h"
|
|
#include "fragglescript/t_fs.h"
|
|
#include "a_keys.h"
|
|
#include "vm.h"
|
|
#include "types.h"
|
|
#include "scriptutil.h"
|
|
|
|
// MACROS ------------------------------------------------------------------
|
|
|
|
// TYPES -------------------------------------------------------------------
|
|
|
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
|
|
|
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
|
|
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
|
|
|
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
|
EXTERN_CVAR(Bool, strictdecorate);
|
|
|
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
|
FMemArena ClassDataAllocator(32768); // use this for all static class data that can be released in bulk when the type system is shut down.
|
|
|
|
TArray<PClass *> PClass::AllClasses;
|
|
TMap<FName, PClass*> PClass::ClassMap;
|
|
TArray<VMFunction**> PClass::FunctionPtrList;
|
|
bool PClass::bShutdown;
|
|
bool PClass::bVMOperational;
|
|
|
|
// Originally this was just a bogus pointer, but with the VM performing a read barrier on every object pointer write
|
|
// that does not work anymore. WP_NOCHANGE needs to point to a vaild object to work as intended.
|
|
// This Object does not need to be garbage collected, though, but it needs to provide the proper structure so that the
|
|
// GC can process it.
|
|
AActor *WP_NOCHANGE;
|
|
DEFINE_GLOBAL(WP_NOCHANGE);
|
|
|
|
|
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
|
|
|
// A harmless non-nullptr FlatPointer for classes without pointers.
|
|
static const size_t TheEnd = ~(size_t)0;
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: WriteValue
|
|
//
|
|
// Similar to PStruct's version, except it also needs to traverse parent
|
|
// classes.
|
|
//
|
|
//==========================================================================
|
|
|
|
static void RecurseWriteFields(const PClass *type, FSerializer &ar, const void *addr)
|
|
{
|
|
if (type != nullptr)
|
|
{
|
|
RecurseWriteFields(type->ParentClass, ar, addr);
|
|
// Don't write this part if it has no non-transient variables
|
|
for (unsigned i = 0; i < type->Fields.Size(); ++i)
|
|
{
|
|
if (!(type->Fields[i]->Flags & (VARF_Transient|VARF_Meta)))
|
|
{
|
|
// Tag this section with the class it came from in case
|
|
// a more-derived class has variables that shadow a less-
|
|
// derived class. Whether or not that is a language feature
|
|
// that will actually be allowed remains to be seen.
|
|
FString key;
|
|
key.Format("class:%s", type->TypeName.GetChars());
|
|
if (ar.BeginObject(key.GetChars()))
|
|
{
|
|
type->VMType->Symbols.WriteFields(ar, addr);
|
|
ar.EndObject();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Same as WriteValue, but does not create a new object in the serializer
|
|
// This is so that user variables do not contain unnecessary subblocks.
|
|
void PClass::WriteAllFields(FSerializer &ar, const void *addr) const
|
|
{
|
|
RecurseWriteFields(this, ar, addr);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: ReadAllFields
|
|
//
|
|
//==========================================================================
|
|
|
|
bool PClass::ReadAllFields(FSerializer &ar, void *addr) const
|
|
{
|
|
bool readsomething = false;
|
|
bool foundsomething = false;
|
|
const char *key;
|
|
key = ar.GetKey();
|
|
if (strcmp(key, "classtype"))
|
|
{
|
|
// this does not represent a DObject
|
|
Printf(TEXTCOLOR_RED "trying to read user variables but got a non-object (first key is '%s')\n", key);
|
|
ar.mErrors++;
|
|
return false;
|
|
}
|
|
while ((key = ar.GetKey()))
|
|
{
|
|
if (strncmp(key, "class:", 6))
|
|
{
|
|
// We have read all user variable blocks.
|
|
break;
|
|
}
|
|
foundsomething = true;
|
|
PClass *type = PClass::FindClass(key + 6);
|
|
if (type != nullptr)
|
|
{
|
|
// Only read it if the type is related to this one.
|
|
if (IsDescendantOf(type))
|
|
{
|
|
if (ar.BeginObject(nullptr))
|
|
{
|
|
readsomething |= type->VMType->Symbols.ReadFields(ar, addr, type->TypeName.GetChars());
|
|
ar.EndObject();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPrintf(DMSG_ERROR, "Unknown superclass %s of class %s\n",
|
|
type->TypeName.GetChars(), TypeName.GetChars());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPrintf(DMSG_ERROR, "Unknown superclass %s of class %s\n",
|
|
key+6, TypeName.GetChars());
|
|
}
|
|
}
|
|
return readsomething || !foundsomething;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// cregcmp
|
|
//
|
|
// Sorter to keep built-in types in a deterministic order. (Needed?)
|
|
//
|
|
//==========================================================================
|
|
|
|
static int cregcmp (const void *a, const void *b) NO_SANITIZE
|
|
{
|
|
const PClass *class1 = *(const PClass **)a;
|
|
const PClass *class2 = *(const PClass **)b;
|
|
return strcmp(class1->TypeName, class2->TypeName);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: StaticInit STATIC
|
|
//
|
|
// Creates class metadata for all built-in types.
|
|
//
|
|
//==========================================================================
|
|
|
|
void PClass::StaticInit ()
|
|
{
|
|
atterm (StaticShutdown);
|
|
|
|
Namespaces.GlobalNamespace = Namespaces.NewNamespace(0);
|
|
|
|
FAutoSegIterator probe(CRegHead, CRegTail);
|
|
|
|
while (*++probe != nullptr)
|
|
{
|
|
((ClassReg *)*probe)->RegisterClass ();
|
|
}
|
|
probe.Reset();
|
|
for(auto cls : AllClasses)
|
|
{
|
|
if (cls->IsDescendantOf(RUNTIME_CLASS(AActor)))
|
|
{
|
|
PClassActor::AllActorClasses.Push(static_cast<PClassActor*>(cls));
|
|
}
|
|
}
|
|
|
|
// Keep built-in classes in consistant order. I did this before, though
|
|
// I'm not sure if this is really necessary to maintain any sort of sync.
|
|
qsort(&AllClasses[0], AllClasses.Size(), sizeof(AllClasses[0]), cregcmp);
|
|
|
|
// WP_NOCHANGE must point to a valid object, although it does not need to be a weapon.
|
|
// A simple DObject is enough to give the GC the ability to deal with it, if subjected to it.
|
|
WP_NOCHANGE = (AActor*)Create<DObject>();
|
|
WP_NOCHANGE->Release();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: StaticShutdown STATIC
|
|
//
|
|
// Frees all static class data.
|
|
//
|
|
//==========================================================================
|
|
|
|
void PClass::StaticShutdown ()
|
|
{
|
|
if (WP_NOCHANGE != nullptr)
|
|
{
|
|
delete WP_NOCHANGE;
|
|
}
|
|
|
|
// delete all variables containing pointers to script functions.
|
|
for (auto p : FunctionPtrList)
|
|
{
|
|
*p = nullptr;
|
|
}
|
|
ScriptUtil::Clear();
|
|
FunctionPtrList.Clear();
|
|
VMFunction::DeleteAll();
|
|
|
|
// Make a full garbage collection here so that all destroyed but uncollected higher level objects
|
|
// that still exist are properly taken down before the low level data is deleted.
|
|
GC::FullGC();
|
|
|
|
// From this point onward no scripts may be called anymore because the data needed by the VM is getting deleted now.
|
|
// This flags DObject::Destroy not to call any scripted OnDestroy methods anymore.
|
|
bVMOperational = false;
|
|
|
|
for (auto &p : players)
|
|
{
|
|
p.PendingWeapon = nullptr;
|
|
}
|
|
Namespaces.ReleaseSymbols();
|
|
|
|
// This must be done in two steps because the native classes are not ordered by inheritance,
|
|
// so all meta data must be gone before deleting the actual class objects.
|
|
for (auto cls : AllClasses) if (cls->Meta != nullptr) cls->DestroyMeta(cls->Meta);
|
|
for (auto cls : AllClasses) delete cls;
|
|
// Unless something went wrong, anything left here should be class and type objects only, which do not own any scripts.
|
|
bShutdown = true;
|
|
TypeTable.Clear();
|
|
ClassDataAllocator.FreeAllBlocks();
|
|
AllClasses.Clear();
|
|
PClassActor::AllActorClasses.Clear();
|
|
ClassMap.Clear();
|
|
|
|
FAutoSegIterator probe(CRegHead, CRegTail);
|
|
|
|
while (*++probe != nullptr)
|
|
{
|
|
auto cr = ((ClassReg *)*probe);
|
|
cr->MyClass = nullptr;
|
|
}
|
|
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass Constructor
|
|
//
|
|
//==========================================================================
|
|
|
|
PClass::PClass()
|
|
{
|
|
PClass::AllClasses.Push(this);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass Destructor
|
|
//
|
|
//==========================================================================
|
|
|
|
PClass::~PClass()
|
|
{
|
|
if (Defaults != nullptr)
|
|
{
|
|
M_Free(Defaults);
|
|
Defaults = nullptr;
|
|
}
|
|
if (Meta != nullptr)
|
|
{
|
|
M_Free(Meta);
|
|
Meta = nullptr;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ClassReg :: RegisterClass
|
|
//
|
|
// Create metadata describing the built-in class this struct is intended
|
|
// for.
|
|
//
|
|
//==========================================================================
|
|
|
|
PClass *ClassReg::RegisterClass()
|
|
{
|
|
// Skip classes that have already been registered
|
|
if (MyClass != nullptr)
|
|
{
|
|
return MyClass;
|
|
}
|
|
|
|
// Add type to list
|
|
PClass *cls = new PClass;
|
|
|
|
SetupClass(cls);
|
|
cls->InsertIntoHash(true);
|
|
if (ParentType != nullptr)
|
|
{
|
|
cls->ParentClass = ParentType->RegisterClass();
|
|
}
|
|
return cls;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ClassReg :: SetupClass
|
|
//
|
|
// Copies the class-defining parameters from a ClassReg to the Class object
|
|
// created for it.
|
|
//
|
|
//==========================================================================
|
|
|
|
void ClassReg::SetupClass(PClass *cls)
|
|
{
|
|
assert(MyClass == nullptr);
|
|
MyClass = cls;
|
|
cls->TypeName = FName(Name+1);
|
|
cls->Size = SizeOf;
|
|
cls->Pointers = Pointers;
|
|
cls->ConstructNative = ConstructNative;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: InsertIntoHash
|
|
//
|
|
// Add class to the type table.
|
|
//
|
|
//==========================================================================
|
|
|
|
void PClass::InsertIntoHash (bool native)
|
|
{
|
|
auto k = ClassMap.CheckKey(TypeName);
|
|
if (k != nullptr)
|
|
{ // This type has already been inserted
|
|
I_Error("Tried to register class '%s' more than once.\n", TypeName.GetChars());
|
|
}
|
|
else
|
|
{
|
|
ClassMap[TypeName] = this;
|
|
}
|
|
if (!native && IsDescendantOf(RUNTIME_CLASS(AActor)))
|
|
{
|
|
PClassActor::AllActorClasses.Push(static_cast<PClassActor*>(this));
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: FindParentClass
|
|
//
|
|
// Finds a parent class that matches the given name, including itself.
|
|
//
|
|
//==========================================================================
|
|
|
|
const PClass *PClass::FindParentClass(FName name) const
|
|
{
|
|
for (const PClass *type = this; type != nullptr; type = type->ParentClass)
|
|
{
|
|
if (type->TypeName == name)
|
|
{
|
|
return type;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: FindClass
|
|
//
|
|
// Find a type, passed the name as a name.
|
|
//
|
|
//==========================================================================
|
|
|
|
PClass *PClass::FindClass (FName zaname)
|
|
{
|
|
if (zaname == NAME_None)
|
|
{
|
|
return nullptr;
|
|
}
|
|
auto k = ClassMap.CheckKey(zaname);
|
|
return k ? *k : nullptr;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: CreateNew
|
|
//
|
|
// Create a new object that this class represents
|
|
//
|
|
//==========================================================================
|
|
|
|
DObject *PClass::CreateNew()
|
|
{
|
|
uint8_t *mem = (uint8_t *)M_Malloc (Size);
|
|
assert (mem != nullptr);
|
|
|
|
// Set this object's defaults before constructing it.
|
|
if (Defaults != nullptr)
|
|
memcpy (mem, Defaults, Size);
|
|
else
|
|
memset (mem, 0, Size);
|
|
|
|
if (ConstructNative == nullptr)
|
|
{
|
|
M_Free(mem);
|
|
I_Error("Attempt to instantiate abstract class %s.", TypeName.GetChars());
|
|
}
|
|
ConstructNative (mem);
|
|
((DObject *)mem)->SetClass (const_cast<PClass *>(this));
|
|
InitializeSpecials(mem, Defaults, &PClass::SpecialInits);
|
|
return (DObject *)mem;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: InitializeSpecials
|
|
//
|
|
// Initialize special fields (e.g. strings) of a newly-created instance.
|
|
//
|
|
//==========================================================================
|
|
|
|
void PClass::InitializeSpecials(void *addr, void *defaults, TArray<FTypeAndOffset> PClass::*Inits)
|
|
{
|
|
// Once we reach a native class, we can stop going up the family tree,
|
|
// since native classes handle initialization natively.
|
|
if ((!bRuntimeClass && Inits == &PClass::SpecialInits) || ParentClass == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
ParentClass->InitializeSpecials(addr, defaults, Inits);
|
|
for (auto tao : (this->*Inits))
|
|
{
|
|
tao.first->InitializeValue((char*)addr + tao.second, defaults == nullptr? nullptr : ((char*)defaults) + tao.second);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: DestroySpecials
|
|
//
|
|
// Destroy special fields (e.g. strings) of an instance that is about to be
|
|
// deleted.
|
|
//
|
|
//==========================================================================
|
|
|
|
void PClass::DestroySpecials(void *addr)
|
|
{
|
|
if (!bRuntimeClass)
|
|
{
|
|
return;
|
|
}
|
|
assert(ParentClass != nullptr);
|
|
ParentClass->DestroySpecials(addr);
|
|
for (auto tao : SpecialInits)
|
|
{
|
|
tao.first->DestroyValue((uint8_t *)addr + tao.second);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: DestroyMeta
|
|
//
|
|
// Same for meta data
|
|
//
|
|
//==========================================================================
|
|
|
|
void PClass::DestroyMeta(void *addr)
|
|
{
|
|
if (ParentClass != nullptr) ParentClass->DestroyMeta(addr);
|
|
for (auto tao : MetaInits)
|
|
{
|
|
tao.first->DestroyValue((uint8_t *)addr + tao.second);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: Derive
|
|
//
|
|
// Copies inheritable values into the derived class and other miscellaneous setup.
|
|
//
|
|
//==========================================================================
|
|
|
|
void PClass::Derive(PClass *newclass, FName name)
|
|
{
|
|
newclass->bRuntimeClass = true;
|
|
newclass->ParentClass = this;
|
|
newclass->ConstructNative = ConstructNative;
|
|
newclass->TypeName = name;
|
|
newclass->MetaSize = MetaSize;
|
|
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClassActor :: InitializeNativeDefaults
|
|
//
|
|
//==========================================================================
|
|
|
|
void PClass::InitializeDefaults()
|
|
{
|
|
if (IsDescendantOf(RUNTIME_CLASS(AActor)))
|
|
{
|
|
assert(Defaults == nullptr);
|
|
Defaults = (uint8_t *)M_Malloc(Size);
|
|
|
|
ConstructNative(Defaults);
|
|
// We must unlink the defaults from the class list because it's just a static block of data to the engine.
|
|
DObject *optr = (DObject*)Defaults;
|
|
GC::Root = optr->ObjNext;
|
|
optr->ObjNext = nullptr;
|
|
optr->SetClass(this);
|
|
|
|
// Copy the defaults from the parent but leave the DObject part alone because it contains important data.
|
|
if (ParentClass->Defaults != nullptr)
|
|
{
|
|
memcpy(Defaults + sizeof(DObject), ParentClass->Defaults + sizeof(DObject), ParentClass->Size - sizeof(DObject));
|
|
if (Size > ParentClass->Size)
|
|
{
|
|
memset(Defaults + ParentClass->Size, 0, Size - ParentClass->Size);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
memset(Defaults + sizeof(DObject), 0, Size - sizeof(DObject));
|
|
}
|
|
|
|
assert(MetaSize >= ParentClass->MetaSize);
|
|
if (MetaSize != 0)
|
|
{
|
|
Meta = (uint8_t*)M_Malloc(MetaSize);
|
|
|
|
// Copy the defaults from the parent but leave the DObject part alone because it contains important data.
|
|
if (ParentClass->Meta != nullptr)
|
|
{
|
|
memcpy(Meta, ParentClass->Meta, ParentClass->MetaSize);
|
|
if (MetaSize > ParentClass->MetaSize)
|
|
{
|
|
memset(Meta + ParentClass->MetaSize, 0, MetaSize - ParentClass->MetaSize);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
memset(Meta, 0, MetaSize);
|
|
}
|
|
|
|
if (MetaSize > 0) memcpy(Meta, ParentClass->Meta, ParentClass->MetaSize);
|
|
else memset(Meta, 0, MetaSize);
|
|
}
|
|
}
|
|
|
|
if (VMType != nullptr) // purely internal classes have no symbol table
|
|
{
|
|
if (bRuntimeClass)
|
|
{
|
|
// Copy parent values from the parent defaults.
|
|
assert(ParentClass != nullptr);
|
|
if (Defaults != nullptr) ParentClass->InitializeSpecials(Defaults, ParentClass->Defaults, &PClass::SpecialInits);
|
|
for (const PField *field : Fields)
|
|
{
|
|
if (!(field->Flags & VARF_Native) && !(field->Flags & VARF_Meta))
|
|
{
|
|
field->Type->SetDefaultValue(Defaults, unsigned(field->Offset), &SpecialInits);
|
|
}
|
|
}
|
|
}
|
|
if (Meta != nullptr) ParentClass->InitializeSpecials(Meta, ParentClass->Meta, &PClass::MetaInits);
|
|
for (const PField *field : Fields)
|
|
{
|
|
if (!(field->Flags & VARF_Native) && (field->Flags & VARF_Meta))
|
|
{
|
|
field->Type->SetDefaultValue(Meta, unsigned(field->Offset), &MetaInits);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: CreateDerivedClass
|
|
//
|
|
// Create a new class based on an existing class
|
|
//
|
|
//==========================================================================
|
|
|
|
PClass *PClass::CreateDerivedClass(FName name, unsigned int size)
|
|
{
|
|
assert(size >= Size);
|
|
PClass *type;
|
|
bool notnew;
|
|
|
|
const PClass *existclass = FindClass(name);
|
|
|
|
if (existclass != nullptr)
|
|
{
|
|
// This is a placeholder so fill it in
|
|
if (existclass->Size == TentativeClass)
|
|
{
|
|
type = const_cast<PClass*>(existclass);
|
|
if (!IsDescendantOf(type->ParentClass))
|
|
{
|
|
I_Error("%s must inherit from %s but doesn't.", name.GetChars(), type->ParentClass->TypeName.GetChars());
|
|
}
|
|
DPrintf(DMSG_SPAMMY, "Defining placeholder class %s\n", name.GetChars());
|
|
notnew = true;
|
|
}
|
|
else
|
|
{
|
|
// a different class with the same name already exists. Let the calling code deal with this.
|
|
return nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
type = new PClass;
|
|
notnew = false;
|
|
}
|
|
|
|
type->TypeName = name;
|
|
type->bRuntimeClass = true;
|
|
Derive(type, name);
|
|
type->Size = size;
|
|
if (size != TentativeClass)
|
|
{
|
|
NewClassType(type);
|
|
type->InitializeDefaults();
|
|
type->Virtuals = Virtuals;
|
|
}
|
|
else
|
|
type->bOptional = false;
|
|
|
|
if (!notnew)
|
|
{
|
|
type->InsertIntoHash(false);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: AddField
|
|
//
|
|
//==========================================================================
|
|
|
|
PField *PClass::AddField(FName name, PType *type, uint32_t flags)
|
|
{
|
|
PField *field;
|
|
if (!(flags & VARF_Meta))
|
|
{
|
|
unsigned oldsize = Size;
|
|
field = VMType->Symbols.AddField(name, type, flags, Size);
|
|
|
|
// Only initialize the defaults if they have already been created.
|
|
// For ZScript this is not the case, it will first define all fields before
|
|
// setting up any defaults for any class.
|
|
if (field != nullptr && !(flags & VARF_Native) && Defaults != nullptr)
|
|
{
|
|
Defaults = (uint8_t *)M_Realloc(Defaults, Size);
|
|
memset(Defaults + oldsize, 0, Size - oldsize);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Same as above, but a different data storage.
|
|
unsigned oldsize = MetaSize;
|
|
field = VMType->Symbols.AddField(name, type, flags, MetaSize);
|
|
|
|
if (field != nullptr && !(flags & VARF_Native) && Meta != nullptr)
|
|
{
|
|
Meta = (uint8_t *)M_Realloc(Meta, MetaSize);
|
|
memset(Meta + oldsize, 0, MetaSize - oldsize);
|
|
}
|
|
}
|
|
if (field != nullptr) Fields.Push(field);
|
|
return field;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: FindClassTentative
|
|
//
|
|
// Like FindClass but creates a placeholder if no class is found.
|
|
// This will be filled in when the actual class is constructed.
|
|
//
|
|
//==========================================================================
|
|
|
|
PClass *PClass::FindClassTentative(FName name)
|
|
{
|
|
if (name == NAME_None)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
PClass *found = FindClass(name);
|
|
if (found != nullptr) return found;
|
|
|
|
PClass *type = new PClass;
|
|
DPrintf(DMSG_SPAMMY, "Creating placeholder class %s : %s\n", name.GetChars(), TypeName.GetChars());
|
|
|
|
Derive(type, name);
|
|
type->Size = TentativeClass;
|
|
|
|
type->InsertIntoHash(false);
|
|
return type;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: FindVirtualIndex
|
|
//
|
|
// Compares a prototype with the existing list of virtual functions
|
|
// and returns an index if something matching is found.
|
|
//
|
|
//==========================================================================
|
|
|
|
int PClass::FindVirtualIndex(FName name, PFunction::Variant *variant, PFunction *parentfunc)
|
|
{
|
|
auto proto = variant->Proto;
|
|
for (unsigned i = 0; i < Virtuals.Size(); i++)
|
|
{
|
|
if (Virtuals[i]->Name == name)
|
|
{
|
|
auto vproto = Virtuals[i]->Proto;
|
|
if (vproto->ReturnTypes.Size() != proto->ReturnTypes.Size() ||
|
|
vproto->ArgumentTypes.Size() < proto->ArgumentTypes.Size())
|
|
{
|
|
|
|
continue; // number of parameters does not match, so it's incompatible
|
|
}
|
|
bool fail = false;
|
|
// The first argument is self and will mismatch so just skip it.
|
|
for (unsigned a = 1; a < proto->ArgumentTypes.Size(); a++)
|
|
{
|
|
if (proto->ArgumentTypes[a] != vproto->ArgumentTypes[a])
|
|
{
|
|
fail = true;
|
|
break;
|
|
}
|
|
}
|
|
if (fail) continue;
|
|
|
|
for (unsigned a = 0; a < proto->ReturnTypes.Size(); a++)
|
|
{
|
|
if (proto->ReturnTypes[a] != vproto->ReturnTypes[a])
|
|
{
|
|
fail = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!fail)
|
|
{
|
|
if (vproto->ArgumentTypes.Size() > proto->ArgumentTypes.Size() && parentfunc)
|
|
{
|
|
// Check if the difference between both functions is only some optional arguments.
|
|
for (unsigned a = proto->ArgumentTypes.Size(); a < vproto->ArgumentTypes.Size(); a++)
|
|
{
|
|
if (!(parentfunc->Variants[0].ArgFlags[a] & VARF_Optional)) return -1;
|
|
}
|
|
|
|
// Todo: extend the prototype
|
|
for (unsigned a = proto->ArgumentTypes.Size(); a < vproto->ArgumentTypes.Size(); a++)
|
|
{
|
|
proto->ArgumentTypes.Push(vproto->ArgumentTypes[a]);
|
|
variant->ArgFlags.Push(parentfunc->Variants[0].ArgFlags[a]);
|
|
variant->ArgNames.Push(NAME_None);
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
PSymbol *PClass::FindSymbol(FName symname, bool searchparents) const
|
|
{
|
|
if (VMType == nullptr) return nullptr;
|
|
return VMType->Symbols.FindSymbol(symname, searchparents);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: BuildFlatPointers
|
|
//
|
|
// Create the FlatPointers array, if it doesn't exist already.
|
|
// It comprises all the Pointers from superclasses plus this class's own
|
|
// Pointers. If this class does not define any new Pointers, then
|
|
// FlatPointers will be set to the same array as the super class.
|
|
//
|
|
//==========================================================================
|
|
|
|
void PClass::BuildFlatPointers ()
|
|
{
|
|
if (FlatPointers != nullptr)
|
|
{ // Already built: Do nothing.
|
|
return;
|
|
}
|
|
else if (ParentClass == nullptr)
|
|
{ // No parent (i.e. DObject: FlatPointers is the same as Pointers.
|
|
if (Pointers == nullptr)
|
|
{ // No pointers: Make FlatPointers a harmless non-nullptr.
|
|
FlatPointers = &TheEnd;
|
|
}
|
|
else
|
|
{
|
|
FlatPointers = Pointers;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ParentClass->BuildFlatPointers ();
|
|
|
|
TArray<size_t> ScriptPointers;
|
|
|
|
// Collect all pointers in scripted fields. These are not part of the Pointers list.
|
|
for (auto field : Fields)
|
|
{
|
|
if (!(field->Flags & VARF_Native))
|
|
{
|
|
field->Type->SetPointer(Defaults, unsigned(field->Offset), &ScriptPointers);
|
|
}
|
|
}
|
|
|
|
if (Pointers == nullptr && ScriptPointers.Size() == 0)
|
|
{ // No new pointers: Just use the same FlatPointers as the parent.
|
|
FlatPointers = ParentClass->FlatPointers;
|
|
}
|
|
else
|
|
{ // New pointers: Create a new FlatPointers array and add them.
|
|
int numPointers, numSuperPointers;
|
|
|
|
if (Pointers != nullptr)
|
|
{
|
|
// Count pointers defined by this class.
|
|
for (numPointers = 0; Pointers[numPointers] != ~(size_t)0; numPointers++)
|
|
{
|
|
}
|
|
}
|
|
else numPointers = 0;
|
|
|
|
// Count pointers defined by superclasses.
|
|
for (numSuperPointers = 0; ParentClass->FlatPointers[numSuperPointers] != ~(size_t)0; numSuperPointers++)
|
|
{ }
|
|
|
|
// Concatenate them into a new array
|
|
size_t *flat = (size_t*)ClassDataAllocator.Alloc(sizeof(size_t) * (numPointers + numSuperPointers + ScriptPointers.Size() + 1));
|
|
if (numSuperPointers > 0)
|
|
{
|
|
memcpy (flat, ParentClass->FlatPointers, sizeof(size_t)*numSuperPointers);
|
|
}
|
|
if (numPointers > 0)
|
|
{
|
|
memcpy(flat + numSuperPointers, Pointers, sizeof(size_t)*numPointers);
|
|
}
|
|
if (ScriptPointers.Size() > 0)
|
|
{
|
|
memcpy(flat + numSuperPointers + numPointers, &ScriptPointers[0], sizeof(size_t) * ScriptPointers.Size());
|
|
}
|
|
flat[numSuperPointers + numPointers + ScriptPointers.Size()] = ~(size_t)0;
|
|
FlatPointers = flat;
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: BuildArrayPointers
|
|
//
|
|
// same as above, but creates a list to dynamic object arrays
|
|
//
|
|
//==========================================================================
|
|
|
|
void PClass::BuildArrayPointers()
|
|
{
|
|
if (ArrayPointers != nullptr)
|
|
{ // Already built: Do nothing.
|
|
return;
|
|
}
|
|
else if (ParentClass == nullptr)
|
|
{ // No parent (i.e. DObject: FlatPointers is the same as Pointers.
|
|
ArrayPointers = &TheEnd;
|
|
}
|
|
else
|
|
{
|
|
ParentClass->BuildArrayPointers();
|
|
|
|
TArray<size_t> ScriptPointers;
|
|
|
|
// Collect all arrays to pointers in scripted fields.
|
|
for (auto field : Fields)
|
|
{
|
|
if (!(field->Flags & VARF_Native))
|
|
{
|
|
field->Type->SetPointerArray(Defaults, unsigned(field->Offset), &ScriptPointers);
|
|
}
|
|
}
|
|
|
|
if (ScriptPointers.Size() == 0)
|
|
{ // No new pointers: Just use the same ArrayPointers as the parent.
|
|
ArrayPointers = ParentClass->ArrayPointers;
|
|
}
|
|
else
|
|
{ // New pointers: Create a new FlatPointers array and add them.
|
|
int numSuperPointers;
|
|
|
|
// Count pointers defined by superclasses.
|
|
for (numSuperPointers = 0; ParentClass->ArrayPointers[numSuperPointers] != ~(size_t)0; numSuperPointers++)
|
|
{
|
|
}
|
|
|
|
// Concatenate them into a new array
|
|
size_t *flat = (size_t*)ClassDataAllocator.Alloc(sizeof(size_t) * (numSuperPointers + ScriptPointers.Size() + 1));
|
|
if (numSuperPointers > 0)
|
|
{
|
|
memcpy(flat, ParentClass->ArrayPointers, sizeof(size_t)*numSuperPointers);
|
|
}
|
|
if (ScriptPointers.Size() > 0)
|
|
{
|
|
memcpy(flat + numSuperPointers, &ScriptPointers[0], sizeof(size_t) * ScriptPointers.Size());
|
|
}
|
|
flat[numSuperPointers + ScriptPointers.Size()] = ~(size_t)0;
|
|
ArrayPointers = flat;
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PClass :: NativeClass
|
|
//
|
|
// Finds the native type underlying this class.
|
|
//
|
|
//==========================================================================
|
|
|
|
const PClass *PClass::NativeClass() const
|
|
{
|
|
const PClass *cls = this;
|
|
|
|
while (cls && cls->bRuntimeClass)
|
|
cls = cls->ParentClass;
|
|
|
|
return cls;
|
|
}
|
|
|
|
VMFunction *PClass::FindFunction(FName clsname, FName funcname)
|
|
{
|
|
auto cls = PClass::FindClass(clsname);
|
|
if (!cls) return nullptr;
|
|
auto func = dyn_cast<PFunction>(cls->FindSymbol(funcname, true));
|
|
if (!func) return nullptr;
|
|
return func->Variants[0].Implementation;
|
|
}
|
|
|
|
void PClass::FindFunction(VMFunction **pptr, FName clsname, FName funcname)
|
|
{
|
|
auto cls = PClass::FindClass(clsname);
|
|
if (!cls) return;
|
|
auto func = dyn_cast<PFunction>(cls->FindSymbol(funcname, true));
|
|
if (!func) return;
|
|
*pptr = func->Variants[0].Implementation;
|
|
FunctionPtrList.Push(pptr);
|
|
}
|
|
|
|
unsigned GetVirtualIndex(PClass *cls, const char *funcname)
|
|
{
|
|
// Look up the virtual function index in the defining class because this may have gotten overloaded in subclasses with something different than a virtual override.
|
|
auto sym = dyn_cast<PFunction>(cls->FindSymbol(funcname, false));
|
|
assert(sym != nullptr);
|
|
auto VIndex = sym->Variants[0].Implementation->VirtualIndex;
|
|
return VIndex;
|
|
}
|
|
|