Class to dynamical create and destroy UDataTable objects at runtime

This code was built and tested with UnReal 5.4.4

I spent the last couple of days reading lots of Q&As that were peripherally related to a problem I needed to solve.

And that is to create UDataTable objects at runtime and delete them if needed.

The short answers gave me some clues. But tying the UDataTable to the Content Browser took a little detective work.

Note that if you are using this class in a Plugin, set "LoadingPhase": "PostEngineInit" in the Modules Section of your plugin.

Below is the class defintion.

// Copyright (C) Brute Squad Games 2024. All Rights Reserved. 
// Provided free for fair use.

#pragma once

#include "AssetToolsModule.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "ContentBrowserModule.h"
#include "Factories/DataTableFactory.h"
#include "IContentBrowserSingleton.h"
#include "ObjectTools.h"
#include "UObject/SavePackage.h"

template <typename DataRecord>
class UBSG_UnrealDataTableManager
{
public:
	UBSG_UnrealDataTableManager(const FString& DataTablePath, const FString& DataTableName);

public:
	bool		CreateTable(void)		const;
	bool		DeleteTable(void)		const;
	bool		DoesTableExist(void)	const;
	UDataTable* GetUDataTable(void)		const;
	bool		MarkDirty(void)			const;

public:
	FString m_DataTablePath;
	FString m_DataTableName;
};

template <typename DataRecord>
UBSG_UnrealDataTableManager<DataRecord>::UBSG_UnrealDataTableManager(const FString& DataTablePath, const FString& DataTableName)
	: m_DataTablePath(DataTablePath)
	, m_DataTableName(DataTableName)
{

}

template <typename DataRecord>
bool UBSG_UnrealDataTableManager<DataRecord>::CreateTable(void) const
{
	FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
	FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
	IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();

	FString PackageName = m_DataTablePath + TEXT("/") + m_DataTableName;

	const FString PackagePath = FPackageName::GetLongPackagePath(PackageName);

	UDataTableFactory* MyFactory = NewObject<UDataTableFactory>(UDataTableFactory::StaticClass()); // Can omit, and a default factory will be used
	MyFactory->Struct = DataRecord::StaticStruct();
	UObject* NewObject = AssetToolsModule.Get().CreateAsset(m_DataTableName, PackagePath, UDataTable::StaticClass(), MyFactory);

	if (nullptr == NewObject)
	{
		return false;
	}

	UPackage* Package = CreatePackage(*PackageName);

	FSavePackageArgs SavePackageArgs;
	UPackage::Save(Package, NewObject, *FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension()), FSavePackageArgs());

	// Inform asset registry
	AssetRegistry.AssetCreated(NewObject);

	auto DataTable = TObjectPtr<UDataTable>(static_cast<UDataTable*>(NewObject));

	FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
	auto& Module = ContentBrowserModule.Get();
	Module.CreateNewAsset(*PackageName, *PackagePath, UDataTable::StaticClass(), MyFactory);
	Module.SyncBrowserToAssets(TArray<UObject*>({ DataTable }), true);

	FAssetRegistryModule::AssetCreated(DataTable);

	return true;
}

template <typename DataRecord>
bool UBSG_UnrealDataTableManager<DataRecord>::DeleteTable(void) const
{
	auto DataTable = GetUDataTable();

	if (nullptr == DataTable)
	{
		return false;
	}

	DataTable->ConditionalBeginDestroy();

	return ObjectTools::DeleteObjects(TArray<UObject*>({ DataTable }), false) ? true : false;
}

template <typename DataRecord>
bool UBSG_UnrealDataTableManager<DataRecord>::DoesTableExist(void) const
{
	try
	{
		return (nullptr != GetUDataTable()) ? true : false;
	}
	catch (...)
	{
		return false;
	}
}

template <typename DataRecord>
UDataTable* UBSG_UnrealDataTableManager<DataRecord>::GetUDataTable(void) const
{
	try
	{
		FName QualifiedPackageName = *(m_DataTablePath + TEXT("/") + m_DataTableName);

		if (QualifiedPackageName == NAME_None)
			return NULL;

		return Cast<UDataTable>(StaticLoadObject(UDataTable::StaticClass(), NULL, *QualifiedPackageName.ToString()));
	}
	catch (...)
	{
		return nullptr;
	}
}

template <typename DataRecord>
bool UBSG_UnrealDataTableManager<DataRecord>::MarkDirty(void)	const
{
	FString PackageName = m_DataTablePath + TEXT("/") + m_DataTableName;

	const FString PackagePath = FPackageName::GetLongPackagePath(PackageName);

	UPackage* Package = CreatePackage(*PackageName);

	return Package->MarkPackageDirty();
}

To use this class, declare it with your FTableRowBase derived class as the template parameter, and pass the path and name of the table in the constructor.

	UBSG_UnrealDataTableManager<FUBSG_DungeonCreatorData_Room> UnrealDataTableManager(TEXT("/Game/DataTables"), TEXT("BSG_RoomsDataTable"));

After this you can call the public functions to easily manipulate your table.

(Note: that though I am an experienced C++ developer, I am new to Unreal. Feel free to offer suggestions, and constructive criticism. I recognize that I may not be doing this the best way, and there may be useless code.)