chore: generalize default asset creators

This commit is contained in:
Jan 2024-12-24 00:58:53 +01:00
parent c524cb007a
commit 9ebea5034a
No known key found for this signature in database
GPG Key ID: 44B581F78FF5C57C
18 changed files with 193 additions and 182 deletions

View File

@ -2,6 +2,9 @@
#include "Zone/ZoneTypes.h" #include "Zone/ZoneTypes.h"
#include <stdexcept>
#include <type_traits>
struct IAssetBase struct IAssetBase
{ {
}; };
@ -12,3 +15,26 @@ public:
static constexpr auto EnumEntry = AssetTypeEnum; static constexpr auto EnumEntry = AssetTypeEnum;
using Type = AssetType; using Type = AssetType;
}; };
template<typename AssetType> struct AssetNameAccessor
{
public:
static_assert(std::is_base_of_v<IAssetBase, AssetType>);
const char*& operator()(AssetType::Type& asset)
{
throw std::runtime_error();
}
};
#define DEFINE_ASSET_NAME_ACCESSOR(assetType, nameProperty) \
template<> struct AssetNameAccessor<assetType> \
{ \
public: \
static_assert(std::is_base_of_v<IAssetBase, assetType>); \
\
const char*& operator()(assetType::Type& asset) \
{ \
return asset.nameProperty; \
} \
}

View File

@ -113,3 +113,32 @@ namespace IW3
using AssetRawFile = Asset<ASSET_TYPE_RAWFILE, RawFile>; using AssetRawFile = Asset<ASSET_TYPE_RAWFILE, RawFile>;
using AssetStringTable = Asset<ASSET_TYPE_STRINGTABLE, StringTable>; using AssetStringTable = Asset<ASSET_TYPE_STRINGTABLE, StringTable>;
} // namespace IW3 } // namespace IW3
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetXModelPieces, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetPhysPreset, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetXAnim, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetXModel, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetMaterial, info.name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetTechniqueSet, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetImage, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetSound, aliasName);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetSoundCurve, filename);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetLoadedSound, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetClipMap, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetClipMapPvs, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetComWorld, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetGameWorldSp, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetGameWorldMp, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetMapEnts, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetGfxWorld, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetLightDef, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetFont, fontName);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetMenuList, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetMenu, window.name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetLocalize, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetWeapon, szInternalName);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetSoundDriverGlobals, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetFx, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetImpactFx, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetRawFile, name);
DEFINE_ASSET_NAME_ACCESSOR(IW3::AssetStringTable, name);

View File

@ -3,6 +3,8 @@
#include <format> #include <format>
#include <iostream> #include <iostream>
IgnoredAssetLookup::IgnoredAssetLookup() = default;
IgnoredAssetLookup::IgnoredAssetLookup(const AssetList& assetList) IgnoredAssetLookup::IgnoredAssetLookup(const AssetList& assetList)
{ {
m_ignored_asset_lookup.reserve(assetList.m_entries.size()); m_ignored_asset_lookup.reserve(assetList.m_entries.size());

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "AssetCreatorCollection.h"
#include "AssetLoading/IZoneAssetLoaderState.h" #include "AssetLoading/IZoneAssetLoaderState.h"
#include "AssetRegistration.h" #include "AssetRegistration.h"
#include "Game/IAsset.h" #include "Game/IAsset.h"
@ -20,6 +19,7 @@ class AssetCreatorCollection;
class IgnoredAssetLookup class IgnoredAssetLookup
{ {
public: public:
IgnoredAssetLookup();
explicit IgnoredAssetLookup(const AssetList& assetList); explicit IgnoredAssetLookup(const AssetList& assetList);
[[nodiscard]] bool IsAssetIgnored(asset_type_t assetType, const std::string& name) const; [[nodiscard]] bool IsAssetIgnored(asset_type_t assetType, const std::string& name) const;
@ -90,3 +90,5 @@ private:
const IgnoredAssetLookup* m_ignored_asset_lookup; const IgnoredAssetLookup* m_ignored_asset_lookup;
std::unordered_map<std::type_index, std::unique_ptr<IZoneAssetLoaderState>> m_zone_asset_loader_states; std::unordered_map<std::type_index, std::unique_ptr<IZoneAssetLoaderState>> m_zone_asset_loader_states;
}; };
#include "AssetCreatorCollection.h"

View File

@ -1,4 +1,4 @@
#include "IAssetCreator.h" #include "AssetCreationResult.h"
AssetCreationResult AssetCreationResult::Success(XAssetInfoGeneric* assetInfo) AssetCreationResult AssetCreationResult::Success(XAssetInfoGeneric* assetInfo)
{ {

View File

@ -0,0 +1,22 @@
#pragma once
#include "Pool/XAssetInfo.h"
class AssetCreationResult
{
public:
static AssetCreationResult Success(XAssetInfoGeneric* assetInfo);
static AssetCreationResult Failure();
static AssetCreationResult NoAction();
[[nodiscard]] bool HasBeenSuccessful() const;
[[nodiscard]] bool HasTakenAction() const;
[[nodiscard]] bool HasFailed() const;
[[nodiscard]] XAssetInfoGeneric* GetAssetInfo() const;
private:
AssetCreationResult(bool takenAction, XAssetInfoGeneric* assetInfo);
bool m_taken_action;
XAssetInfoGeneric* m_asset_info;
};

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "AssetCreationContext.h" #include "AssetCreationContext.h"
#include "AssetCreationResult.h"
#include "Game/IAsset.h" #include "Game/IAsset.h"
#include "Pool/XAssetInfo.h" #include "Pool/XAssetInfo.h"
#include "SearchPath/ISearchPath.h" #include "SearchPath/ISearchPath.h"
@ -10,25 +11,6 @@
#include <optional> #include <optional>
#include <string> #include <string>
class AssetCreationResult
{
public:
static AssetCreationResult Success(XAssetInfoGeneric* assetInfo);
static AssetCreationResult Failure();
static AssetCreationResult NoAction();
[[nodiscard]] bool HasBeenSuccessful() const;
[[nodiscard]] bool HasTakenAction() const;
[[nodiscard]] bool HasFailed() const;
[[nodiscard]] XAssetInfoGeneric* GetAssetInfo() const;
private:
AssetCreationResult(bool takenAction, XAssetInfoGeneric* assetInfo);
bool m_taken_action;
XAssetInfoGeneric* m_asset_info;
};
class AssetCreationContext; class AssetCreationContext;
class IAssetCreator class IAssetCreator

View File

@ -1,16 +1,15 @@
#pragma once #pragma once
#include "AssetCreationContext.h" #include "AssetCreationContext.h"
#include "AssetCreationResult.h"
#include "Game/IAsset.h" #include "Game/IAsset.h"
#include "IAssetCreator.h" #include "IAssetCreator.h"
#include "Utils/MemoryManager.h"
#include "Zone/ZoneTypes.h" #include "Zone/ZoneTypes.h"
#include <string> #include <string>
#include <type_traits> #include <type_traits>
class AssetCreationResult;
class AssetCreationContext;
class IDefaultAssetCreator class IDefaultAssetCreator
{ {
public: public:
@ -30,8 +29,24 @@ template<typename AssetType> class DefaultAssetCreator : public IDefaultAssetCre
public: public:
static_assert(std::is_base_of_v<IAssetBase, AssetType>); static_assert(std::is_base_of_v<IAssetBase, AssetType>);
DefaultAssetCreator(MemoryManager& memory)
: m_memory(memory)
{
}
[[nodiscard]] asset_type_t GetHandlingAssetType() const override [[nodiscard]] asset_type_t GetHandlingAssetType() const override
{ {
return AssetType::EnumEntry; return AssetType::EnumEntry;
} }
AssetCreationResult CreateDefaultAsset(const std::string& assetName, AssetCreationContext& context) const override
{
auto* asset = m_memory.Alloc<typename AssetType::Type>();
AssetNameAccessor<AssetType>{}(*asset) = m_memory.Dup(assetName.c_str());
return AssetCreationResult::Success(context.AddAsset<AssetType>(assetName, asset));
}
private:
MemoryManager& m_memory;
}; };

View File

@ -1,39 +0,0 @@
#include "AssetLoaderStringTable.h"
#include "Csv/CsvStream.h"
#include "Game/IW3/CommonIW3.h"
#include "ObjLoading.h"
#include "Pool/GlobalAssetPool.h"
#include "StringTable/StringTableLoader.h"
#include <cstring>
using namespace IW3;
void* AssetLoaderStringTable::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory)
{
auto* stringTable = memory->Create<StringTable>();
memset(stringTable, 0, sizeof(StringTable));
stringTable->name = memory->Dup(assetName.c_str());
return stringTable;
}
bool AssetLoaderStringTable::CanLoadFromRaw() const
{
return true;
}
bool AssetLoaderStringTable::LoadFromRaw(
const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const
{
const auto file = searchPath->Open(assetName);
if (!file.IsOpen())
return false;
string_table::StringTableLoaderV1<StringTable> loader;
auto* stringTable = loader.LoadFromStream(assetName, *memory, *file.m_stream);
manager->AddAsset<AssetStringTable>(assetName, stringTable);
return true;
}

View File

@ -1,17 +0,0 @@
#pragma once
#include "AssetLoading/BasicAssetLoader.h"
#include "Game/IW3/IW3.h"
#include "SearchPath/ISearchPath.h"
namespace IW3
{
class AssetLoaderStringTable final : public BasicAssetLoader<AssetStringTable>
{
public:
_NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override;
_NODISCARD bool CanLoadFromRaw() const override;
bool
LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const override;
};
} // namespace IW3

View File

@ -1,16 +0,0 @@
#include "DefaultCreatorImageIW3.h"
using namespace IW3;
DefaultCreatorImage::DefaultCreatorImage(MemoryManager& memory)
: m_memory(memory)
{
}
AssetCreationResult DefaultCreatorImage::CreateDefaultAsset(const std::string& assetName, AssetCreationContext& context) const
{
auto* asset = m_memory.Alloc<GfxImage>();
asset->name = m_memory.Dup(assetName.c_str());
return AssetCreationResult::Success(context.AddAsset<AssetImage>(assetName, asset));
}

View File

@ -1,18 +0,0 @@
#pragma once
#include "Asset/IDefaultAssetCreator.h"
#include "Game/IW3/IW3.h"
#include "Utils/MemoryManager.h"
namespace IW3
{
class DefaultCreatorImage : public DefaultAssetCreator<AssetImage>
{
public:
explicit DefaultCreatorImage(MemoryManager& memory);
AssetCreationResult CreateDefaultAsset(const std::string& assetName, AssetCreationContext& context) const override;
private:
MemoryManager& m_memory;
};
} // namespace IW3

View File

@ -4,7 +4,6 @@
#include "Game/IW3/GameIW3.h" #include "Game/IW3/GameIW3.h"
#include "Game/IW3/IW3.h" #include "Game/IW3/IW3.h"
#include "Image/AssetLoaderImageIW3.h" #include "Image/AssetLoaderImageIW3.h"
#include "Image/DefaultCreatorImageIW3.h"
#include "Localize/AssetLoaderLocalizeIW3.h" #include "Localize/AssetLoaderLocalizeIW3.h"
#include "ObjLoading.h" #include "ObjLoading.h"
@ -22,33 +21,33 @@ namespace
{ {
auto& memory = *zone.GetMemory(); auto& memory = *zone.GetMemory();
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorPhysPreset>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetPhysPreset>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorXAnim>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetXAnim>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorXModel>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetXModel>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorMaterial>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetMaterial>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorTechniqueSet>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetTechniqueSet>>(memory));
collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorImage>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetImage>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorSound>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetSound>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorSoundCurve>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetSoundCurve>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorLoadedSound>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetLoadedSound>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorClipMap>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetClipMap>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorClipMapPvs>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetClipMapPvs>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorComWorld>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetComWorld>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorGameWorldSp>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetGameWorldSp>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorGameWorldMp>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetGameWorldMp>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorMapEnts>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetMapEnts>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorGfxWorld>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetGfxWorld>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorLightDef>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetLightDef>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorFont>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetFont>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorMenuList>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetMenuList>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorMenu>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetMenu>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorLocalize>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetLocalize>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorWeapon>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetWeapon>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorSoundDriverGlobals>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetSoundDriverGlobals>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorFx>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetFx>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorImpactFx>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetImpactFx>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorRawFile>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetRawFile>>(memory));
// collection.AddDefaultAssetCreator(std::make_unique<DefaultCreatorStringTable>(memory)); collection.AddDefaultAssetCreator(std::make_unique<DefaultAssetCreator<AssetStringTable>>(memory));
} }
void ConfigureGlobalAssetPoolsLoaders(AssetCreatorCollection& collection, Zone& zone) void ConfigureGlobalAssetPoolsLoaders(AssetCreatorCollection& collection, Zone& zone)

View File

@ -1,16 +0,0 @@
#include "DefaultCreatorRawFileIW3.h"
using namespace IW3;
DefaultCreatorRawFile::DefaultCreatorRawFile(MemoryManager& memory)
: m_memory(memory)
{
}
AssetCreationResult DefaultCreatorRawFile::CreateDefaultAsset(const std::string& assetName, AssetCreationContext& context) const
{
auto* asset = m_memory.Alloc<RawFile>();
asset->name = m_memory.Dup(assetName.c_str());
return AssetCreationResult::Success(context.AddAsset<AssetRawFile>(assetName, asset));
}

View File

@ -1,18 +0,0 @@
#pragma once
#include "Asset/IDefaultAssetCreator.h"
#include "Game/IW3/IW3.h"
#include "Utils/MemoryManager.h"
namespace IW3
{
class DefaultCreatorRawFile : public DefaultAssetCreator<AssetRawFile>
{
public:
explicit DefaultCreatorRawFile(MemoryManager& memory);
AssetCreationResult CreateDefaultAsset(const std::string& assetName, AssetCreationContext& context) const override;
private:
MemoryManager& m_memory;
};
} // namespace IW3

View File

@ -0,0 +1,29 @@
#include "AssetLoaderStringTableIW3.h"
#include "Game/IW3/IW3.h"
#include "Pool/GlobalAssetPool.h"
#include "StringTable/StringTableLoader.h"
#include <cstring>
using namespace IW3;
AssetLoaderStringTable::AssetLoaderStringTable(MemoryManager& memory, ISearchPath& searchPath)
: m_memory(memory),
m_search_path(searchPath)
{
}
AssetCreationResult AssetLoaderStringTable::CreateAsset(const std::string& assetName, AssetCreationContext& context)
{
const auto file = m_search_path.Open(assetName);
if (!file.IsOpen())
return AssetCreationResult::NoAction();
string_table::StringTableLoaderV1<StringTable> loader;
auto* stringTable = loader.LoadFromStream(assetName, m_memory, *file.m_stream);
if (!stringTable)
return AssetCreationResult::Failure();
return AssetCreationResult::Success(context.AddAsset<AssetStringTable>(assetName, stringTable));
}

View File

@ -0,0 +1,21 @@
#pragma once
#include "Asset/IAssetCreator.h"
#include "Game/IW3/IW3.h"
#include "SearchPath/ISearchPath.h"
#include "Utils/MemoryManager.h"
namespace IW3
{
class AssetLoaderStringTable final : public AssetCreator<AssetRawFile>
{
public:
AssetLoaderStringTable(MemoryManager& memory, ISearchPath& searchPath);
AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override;
private:
MemoryManager& m_memory;
ISearchPath& m_search_path;
};
} // namespace IW3

View File

@ -1,8 +1,9 @@
#include "Game/IW3/AssetLoaders/AssetLoaderStringTable.h" #include "Game/IW3/StringTable/AssetLoaderStringTableIW3.h"
#include "Game/IW3/GameIW3.h" #include "Game/IW3/GameIW3.h"
#include "Mock/MockAssetLoadingManager.h" #include "Mock/MockAssetLoadingManager.h"
#include "Mock/MockSearchPath.h" #include "Mock/MockSearchPath.h"
#include "Pool/ZoneAssetPools.h"
#include "Utils/MemoryManager.h" #include "Utils/MemoryManager.h"
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
@ -21,16 +22,23 @@ namespace
"lorem,ipsum"); "lorem,ipsum");
Zone zone("MockZone", 0, IGame::GetGameById(GameId::IW3)); Zone zone("MockZone", 0, IGame::GetGameById(GameId::IW3));
MockAssetLoadingManager assetLoadingManager(zone, searchPath); zone.m_pools = ZoneAssetPools::CreateForGame(GameId::IW3, &zone, 1);
const auto assetTypeCount = zone.m_pools->GetAssetTypeCount();
for (auto i = 0; i < assetTypeCount; i++)
zone.m_pools->InitPoolDynamic(i);
AssetLoaderStringTable assetLoader;
MemoryManager memory; MemoryManager memory;
AssetCreatorCollection creatorCollection(zone);
AssetLoaderStringTable assetLoader(memory, searchPath);
IgnoredAssetLookup ignoredAssetLookup;
assetLoader.LoadFromRaw("mp/cooltable.csv", &searchPath, &memory, &assetLoadingManager, &zone); AssetCreationContext context(&zone, &creatorCollection, &ignoredAssetLookup);
auto* assetInfo = reinterpret_cast<XAssetInfo<StringTable>*>(assetLoadingManager.MockGetAddedAsset("mp/cooltable.csv")); const auto result = assetLoader.CreateAsset("mp/cooltable.csv", context);
REQUIRE(assetInfo != nullptr); REQUIRE(result.HasBeenSuccessful());
const auto* assetInfo = reinterpret_cast<XAssetInfo<StringTable>*>(result.GetAssetInfo());
const auto* stringTable = assetInfo->Asset(); const auto* stringTable = assetInfo->Asset();
REQUIRE(stringTable->name == "mp/cooltable.csv"s); REQUIRE(stringTable->name == "mp/cooltable.csv"s);
REQUIRE(stringTable->columnCount == 3); REQUIRE(stringTable->columnCount == 3);