New templates

This commit is contained in:
Aruki
2018-10-07 21:47:18 -06:00
parent 84d689e104
commit 803ea5788b
2177 changed files with 167624 additions and 100306 deletions

View File

@@ -170,7 +170,7 @@ public:
inline void SetVisible(bool Visible) { mVisible = Visible; }
inline void MarkDirty() { mDirty = true; }
inline bool IsDirty() const { return mDirty; }
inline bool IsDirty() const { return mDirty || mpProperties->IsDirty(); }
// Object Tracking
u32 NumObjects() const;

View File

@@ -0,0 +1,171 @@
#include "NGameList.h"
namespace NGameList
{
/** Path for the templates directory */
const TString gkTemplatesDir = "../templates/";
/** Path to the game list file */
const TString gkGameListPath = gkTemplatesDir + "GameList.xml";
/** Info about a particular game serialized to the list */
struct SGameInfo
{
TString Name;
TString TemplatePath;
std::unique_ptr<CGameTemplate> pTemplate;
bool IsValid;
SGameInfo()
: IsValid(false)
{}
void Serialize(IArchive& Arc)
{
Arc << SerialParameter("Name", Name)
<< SerialParameter("GameTemplate", TemplatePath);
if (Arc.IsReader())
{
IsValid = true;
}
}
};
SGameInfo gGameList[EGame::Max];
/** Whether the game list has been loaded */
bool gLoadedGameList = false;
/** Returns whether a game template has been loaded or not */
bool IsGameTemplateLoaded(EGame Game)
{
int GameIdx = (int) Game;
const SGameInfo& GameInfo = gGameList[GameIdx];
return GameInfo.pTemplate != nullptr;
}
/** Serialize the game list to/from a file */
inline void SerializeGameList(IArchive& Arc)
{
// Serialize the number of games with valid GameInfos.
u32 NumGames = 0;
if (Arc.IsWriter())
{
for (u32 GameIdx = 0; GameIdx < (u32) EGame::Max; GameIdx++)
{
if ( gGameList[GameIdx].IsValid )
NumGames++;
}
}
Arc.SerializeArraySize(NumGames);
// Serialize the actual game info
for (u32 GameIdx = 0; GameIdx < (u32) EGame::Max; GameIdx++)
{
// Skip games that don't have game templates when writing.
if (Arc.IsWriter() && !gGameList[GameIdx].IsValid)
continue;
ENSURE( Arc.ParamBegin("Game", 0) );
// Determine which game is being serialized
EGame Game = (EGame) GameIdx;
Arc << SerialParameter("ID", Game, SH_Attribute);
ASSERT( Game != EGame::Invalid );
gGameList[ (u32) Game ].Serialize(Arc);
Arc.ParamEnd();
}
}
/** Load the game list into memory */
void LoadGameList()
{
ASSERT(!gLoadedGameList);
CXMLReader Reader(gkGameListPath);
ASSERT(Reader.IsValid());
SerializeGameList(Reader);
gLoadedGameList = true;
}
/** Save the game list back out to a file */
void SaveGameList()
{
ASSERT(gLoadedGameList);
CXMLWriter Writer(gkGameListPath, "GameList");
ASSERT(Writer.IsValid());
SerializeGameList(Writer);
}
/** Load all game templates into memory */
void LoadAllGameTemplates()
{
for (int GameIdx = 0; GameIdx < (int) EGame::Max; GameIdx++)
GetGameTemplate( (EGame) GameIdx );
}
/** Resave templates. If ForceAll is false, only saves templates that have been modified. */
void SaveTemplates(bool ForceAll /*= false*/)
{
for (int GameIdx = 0; GameIdx < (int) EGame::Max; GameIdx++)
{
EGame Game = (EGame) GameIdx;
if ( IsGameTemplateLoaded(Game) )
{
CGameTemplate* pGameTemplate = GetGameTemplate(Game);
pGameTemplate->SaveGameTemplates(ForceAll);
}
}
}
/** Get the game template for a given game */
CGameTemplate* GetGameTemplate(EGame Game)
{
// Game must be valid!
if (Game == EGame::Invalid)
{
return nullptr;
}
ASSERT(Game >= (EGame) 0 && Game < EGame::Max);
// Initialize the game list, if it hasn't been loaded yet.
if (!gLoadedGameList)
{
LoadGameList();
}
int GameIdx = (int) Game;
SGameInfo& GameInfo = gGameList[GameIdx];
// Load the game template, if it hasn't been loaded yet.
if (!GameInfo.pTemplate && !GameInfo.Name.IsEmpty())
{
TString GamePath = gkTemplatesDir + GameInfo.TemplatePath;
GameInfo.pTemplate = std::make_unique<CGameTemplate>();
GameInfo.pTemplate->Load(GamePath);
}
return GameInfo.pTemplate.get();
}
/** Clean up game list resources. This needs to be called on app shutdown to ensure things are cleaned up in the right order. */
void Shutdown()
{
for (int GameIdx = 0; GameIdx < (int) EGame::Max; GameIdx++)
{
gGameList[GameIdx].Name = "";
gGameList[GameIdx].TemplatePath = "";
gGameList[GameIdx].pTemplate = nullptr;
}
gLoadedGameList = false;
}
}

View File

@@ -0,0 +1,32 @@
#ifndef NGAMELIST_H
#define NGAMELIST_H
#include "CGameTemplate.h"
namespace NGameList
{
/** Load all game templates into memory
* This normally isn't necessary to call, as game templates will be lazy-loaded the
* first time they are requested.
*/
void LoadAllGameTemplates();
/** Load the game list into memory. This is normally not necessary to call. */
void LoadGameList();
/** Save the game list back out to a file */
void SaveGameList();
/** Resave templates. If ForceAll is false, only saves templates that have been modified. */
void SaveTemplates(bool ForceAll = false);
/** Get the game template for a given game */
CGameTemplate* GetGameTemplate(EGame Game);
/** Clean up game list resources. This needs to be called on app shutdown to ensure things are cleaned up in the right order. */
void Shutdown();
}
#endif // NGAMELIST_H

View File

@@ -0,0 +1,317 @@
#include "NPropertyMap.h"
#include <Common/NBasics.h>
#include <Common/Serialization/XML.h>
/** NPropertyMap: Namespace for property ID -> name mappings */
namespace NPropertyMap
{
/** Path to the property map file */
const char* gpkLegacyMapPath = "../templates/PropertyMapLegacy.xml";
const char* gpkMapPath = "../templates/PropertyMap.xml";
/** Whether to do name lookups from the legacy map */
const bool gkUseLegacyMapForNameLookups = false;
/** Whether to update names in the legacy map */
const bool gkUseLegacyMapForUpdates = false;
/** Whether the map is dirty (has unsaved changes */
bool gMapIsDirty = false;
/** Whether the map has been loaded */
bool gMapIsLoaded = false;
/** Mapping of typename hashes back to the original string */
std::unordered_map<u32, TString> gHashToTypeName;
/** Register a hash -> name mapping */
inline void RegisterTypeName(u32 TypeHash, TString TypeName)
{
ASSERT( !TypeName.IsEmpty() );
ASSERT( TypeName != "Unknown" );
gHashToTypeName.emplace( std::make_pair<u32, TString>(std::move(TypeHash), std::move(TypeName)) );
}
/** Key structure for name map lookups */
struct SNameKey
{
union
{
struct {
u32 TypeHash;
u32 ID;
};
struct {
u64 Key;
};
};
SNameKey()
: TypeHash(-1), ID(-1)
{}
SNameKey(u32 InTypeHash, u32 InID)
: TypeHash(InTypeHash), ID(InID)
{}
void Serialize(IArchive& Arc)
{
TString TypeName;
if (Arc.IsWriter())
{
TypeName = gHashToTypeName[TypeHash];
ASSERT(!TypeName.IsEmpty());
}
Arc << SerialParameter("ID", ID, SH_Attribute | SH_HexDisplay)
<< SerialParameter("Type", TypeName, SH_Attribute);
if (Arc.IsReader())
{
TypeHash = TypeName.Hash32();
RegisterTypeName(TypeHash, TypeName);
}
}
friend bool operator==(const SNameKey& kLHS, const SNameKey& kRHS)
{
return kLHS.Key == kRHS.Key;
}
friend bool operator<(const SNameKey& kLHS, const SNameKey& kRHS)
{
return kLHS.Key < kRHS.Key;
}
};
/** Hasher for name keys for use in std::unordered_map */
struct KeyHash
{
inline size_t operator()(const SNameKey& kKey) const
{
return std::hash<u64>()(kKey.Key);
}
};
/** Value structure for name map lookups */
struct SNameValue
{
/** Name of the property */
TString Name;
/** List of all properties using this ID */
std::list<IProperty*> PropertyList;
void Serialize(IArchive& Arc)
{
Arc << SerialParameter("Name", Name, SH_Attribute);
}
friend bool operator==(const SNameValue& kLHS, const SNameValue& kRHS)
{
return kLHS.Name == kRHS.Name;
}
};
/** Mapping of property IDs to names. In the key, the upper 32 bits
* are the type, and the lower 32 bits are the ID.
*/
std::map<SNameKey, SNameValue> gNameMap;
/** Legacy map that only includes the ID in the key */
std::map<u32, TString> gLegacyNameMap;
/** Internal: Creates a name key for the given property. */
SNameKey CreateKey(IProperty* pProperty)
{
SNameKey Key;
Key.ID = pProperty->ID();
Key.TypeHash = CCRC32::StaticHashString( pProperty->HashableTypeName() );
return Key;
}
/** Loads property names into memory */
void LoadMap()
{
ASSERT( !gMapIsLoaded );
if ( gkUseLegacyMapForNameLookups )
{
CXMLReader Reader(gpkLegacyMapPath);
ASSERT(Reader.IsValid());
Reader << SerialParameter("PropertyMap", gLegacyNameMap, SH_HexDisplay);
}
else
{
CXMLReader Reader(gpkMapPath);
ASSERT(Reader.IsValid());
Reader << SerialParameter("PropertyMap", gNameMap, SH_HexDisplay);
}
gMapIsLoaded = true;
}
inline void ConditionalLoadMap()
{
if( !gMapIsLoaded )
{
LoadMap();
}
}
/** Saves property names back out to the template file */
void SaveMap(bool Force /*= false*/)
{
ASSERT( gMapIsLoaded );
if( gMapIsDirty || Force )
{
if( gkUseLegacyMapForUpdates )
{
CXMLWriter Writer(gpkLegacyMapPath, "PropertyMap");
ASSERT(Writer.IsValid());
Writer << SerialParameter("PropertyMap", gLegacyNameMap, SH_HexDisplay);
}
else
{
CXMLWriter Writer(gpkMapPath, "PropertyMap");
ASSERT(Writer.IsValid());
Writer << SerialParameter("PropertyMap", gNameMap, SH_HexDisplay);
}
gMapIsDirty = false;
}
}
/** Given a property ID and type, returns the name of the property */
const char* GetPropertyName(IProperty* pInProperty)
{
ConditionalLoadMap();
if (gkUseLegacyMapForNameLookups)
{
auto MapFind = gLegacyNameMap.find( pInProperty->ID() );
return (MapFind == gLegacyNameMap.end() ? "Unknown" : *MapFind->second);
}
else
{
SNameKey Key = CreateKey(pInProperty);
auto MapFind = gNameMap.find(Key);
return (MapFind == gNameMap.end() ? "Unknown" : *MapFind->second.Name);
}
}
/** Given a property name and type, returns the name of the property.
* This requires you to provide the exact type string used in the hash.
*/
const char* GetPropertyName(u32 ID, const char* pkTypeName)
{
// Does not support legacy map
ConditionalLoadMap();
SNameKey Key( CCRC32::StaticHashString(pkTypeName), ID );
auto MapFind = gNameMap.find(Key);
return MapFind == gNameMap.end() ? "Unknown" : *MapFind->second.Name;
}
/** Updates the name of a given property in the map */
void SetPropertyName(IProperty* pProperty, const char* pkNewName)
{
if( gkUseLegacyMapForUpdates )
{
gLegacyNameMap[pProperty->ID()] = pkNewName;
}
else
{
SNameKey Key = CreateKey(pProperty);
auto MapFind = gNameMap.find(Key);
if (MapFind != gNameMap.end())
{
SNameValue& Value = MapFind->second;
if (Value.Name != pkNewName)
{
TString OldName = Value.Name;
Value.Name = pkNewName;
// Update all properties with this ID with the new name
for (auto Iter = Value.PropertyList.begin(); Iter != Value.PropertyList.end(); Iter++)
{
// If the property overrides the name, then don't change it.
IProperty* pIterProperty = *Iter;
if (pIterProperty->Name() == OldName)
{
pIterProperty->SetName(Value.Name);
}
}
}
}
}
gMapIsDirty = true;
}
/** Registers a property in the name map. Should be called on all properties that use the map */
void RegisterProperty(IProperty* pProperty)
{
ConditionalLoadMap();
// Sanity checks to make sure we don't accidentally add non-hash property IDs to the map.
ASSERT( pProperty->UsesNameMap() );
ASSERT( pProperty->ID() > 0xFF && pProperty->ID() != 0xFFFFFFFF );
// Just need to register the property in the list.
SNameKey Key = CreateKey(pProperty);
auto MapFind = gNameMap.find(Key);
if( gkUseLegacyMapForNameLookups )
{
// If we are using the legacy map, gNameMap may be empty. We need to retrieve the name
// from the legacy map, and create an entry in gNameMap with it.
//@todo this prob isn't the most efficient way to do this
if (MapFind == gNameMap.end())
{
auto LegacyMapFind = gLegacyNameMap.find( pProperty->ID() );
ASSERT( LegacyMapFind != gLegacyNameMap.end() );
SNameValue Value;
Value.Name = LegacyMapFind->second;
pProperty->SetName(Value.Name);
gNameMap[Key] = Value;
MapFind = gNameMap.find(Key);
RegisterTypeName(Key.TypeHash, pProperty->HashableTypeName());
}
}
else
{
pProperty->SetName( MapFind->second.Name );
}
ASSERT(MapFind != gNameMap.end());
MapFind->second.PropertyList.push_back(pProperty);
// Update the property's Name field to match the mapped name.
pProperty->SetName( MapFind->second.Name );
}
/** Unregisters a property from the name map. Should be called on all properties that use the map on destruction. */
void UnregisterProperty(IProperty* pProperty)
{
SNameKey Key = CreateKey(pProperty);
auto Iter = gNameMap.find(Key);
if (Iter != gNameMap.end())
{
// Found the value, now remove the element from the list.
SNameValue& Value = Iter->second;
NBasics::ListRemoveOne(Value.PropertyList, pProperty);
}
}
}

View File

@@ -0,0 +1,36 @@
#ifndef NPROPERTYMAP_H
#define NPROPERTYMAP_H
#include <Common/types.h>
#include "Core/Resource/Script/Property/IProperty.h"
/** NPropertyMap: Namespace for property ID -> name mappings */
namespace NPropertyMap
{
/** Loads property names into memory */
void LoadMap();
/** Saves property names back out to the template file */
void SaveMap(bool Force = false);
/** Returns the name of the property */
const char* GetPropertyName(IProperty* pProperty);
/** Given a property name and type, returns the name of the property.
* This requires you to provide the exact type string used in the hash.
*/
const char* GetPropertyName(u32 ID, const char* pkTypeName);
/** Updates the name of a given property in the map */
void SetPropertyName(IProperty* pProperty, const char* pkNewName);
/** Registers a property in the name map. Should be called on all properties that use the map */
void RegisterProperty(IProperty* pProperty);
/** Unregisters a property from the name map. Should be called on all properties that use the map on destruction. */
void UnregisterProperty(IProperty* pProperty);
}
#endif // NPROPERTYMAP_H

View File

@@ -79,12 +79,16 @@ void CStructProperty::Serialize(IArchive& rArc)
IProperty::Serialize(rArc);
// Serialize atomic flag
bool Atomic = IsAtomic();
rArc << SerialParameter("Atomic", Atomic, SH_Optional, false);
if (rArc.IsReader() && Atomic)
// Only serialize this if we don't have an archetype. Otherwise we just inherit the archetype's atomic flag.
if (!mpArchetype)
{
mFlags.SetFlag(EPropertyFlag::IsAtomic);
bool Atomic = IsAtomic();
rArc << SerialParameter("Atomic", Atomic, SH_Optional, false);
if (rArc.IsReader() && Atomic)
{
mFlags.SetFlag(EPropertyFlag::IsAtomic);
}
}
// Serialize archetype

View File

@@ -177,6 +177,13 @@ void IProperty::Initialize(IProperty* pInParent, CScriptTemplate* pInTemplate, u
{
mFlags |= EPropertyFlag::IsArrayArchetype;
}
// MP1 has some weirdness we need to account for, most likely due to incorrect templates
// The templates we have right now have non-atomic structs inside atomic structs...
if (Game() <= EGame::Prime && mpParent->IsAtomic() && mpArchetype && !mpArchetype->IsAtomic())
{
mFlags.ClearFlag(EPropertyFlag::IsAtomic);
}
}
else if (!mpScriptTemplate)
{
@@ -322,16 +329,7 @@ void IProperty::SetSuffix(const TString& rkNewSuffix)
void IProperty::MarkDirty()
{
// This property is either part of a script template, or a property archetype.
// Figure out which one, set the dirty flag as needed
if (mpScriptTemplate)
{
mpScriptTemplate->MarkDirty();
}
else
{
RootParent()->mFlags |= EPropertyFlag::IsDirty;
}
RootParent()->mFlags |= EPropertyFlag::IsDirty;
}
void IProperty::ClearDirtyFlag()