Download

How does Rama's binary save work?

I’m doing Rama’s tutorial, https://wiki.unrealengine.com/Save_System,Read%26_Write_Any_Data_to_Compressed_Binary_Files and it seems really awesome. I do not get any crashes and everything seems be working 100%. The only problem is that… I do not actually understand what is happening with the variables.

I understand the variables be converted to binary arrays, saved to disk and vice versa. I do not understand HOW it actually gets the variables nor do I understand where the new variables go. Can someone please tell me what I am doing wrong or what actually is happening to the variables?

My .h


#pragma once

#include "GameFramework/SaveGame.h"
#include "FileTesting.generated.h"

/**
 * 
 */
UCLASS()
class BP_CRASH_TEST_API UFileTesting : public USaveGame
{
	GENERATED_BODY()
	
public:
		UFUNCTION(BlueprintCallable, Category = "1_Custom")
		void FileManagerTest();
	

		void SaveLoadTesting(FArchive& Ar, int32& SaveDataInt32, FString& SaveStringData);
		bool SaveLoadTestingM(const FString& FullFilePath, FBufferArchive& ToBinary);
		FBufferArchive TToBinary;
		FString EmptyString = "Testing";
		int32 EmptyInt = 20;
		FString FilePathG = "C:\	est\	est";

		UFUNCTION(BlueprintCallable, Category = "1_Custom")
		void CallFile();

		UFUNCTION(BlueprintCallable, Category = "1_Custom")
		void LoadFile();

		bool LLoadTesting(const FString& FullFilePath, int32& SaveDataInt32, FString& SaveStringData);

};


My .cpp


#include "BP_crash_test.h"
#include "FileTesting.h"




void UFileTesting::FileManagerTest()
{
	FString FilePathG = "C:\\AdwCleaner";
	TArray<FString> output;
	output.Empty();
	IFileManager& FileManagerM = IFileManager::Get();
	FileManagerM.FindFiles(output, *FilePathG);

	int32 FillerString = output.Num();
	UE_LOG(LogTemp, Warning, TEXT("whaattt %d "), FillerString);
	UE_LOG(LogTemp, Warning, TEXT("whaattt %s "), *output[0]);


}

void UFileTesting::SaveLoadTesting(FArchive& Ar, int32& SaveDataInt32, FString& SaveStringData)
{


	// I do not understand how this saves things at all or what this even does.
	Ar << SaveDataInt32;
	Ar << SaveStringData;

}



//This function takes data and saves it to binary arrays on disk.
bool UFileTesting::SaveLoadTestingM(const FString& FullFilePath, FBufferArchive& ToBinary)
{

	SaveLoadTesting(ToBinary, EmptyInt, EmptyString);


	if (ToBinary.Num() <= 0)
	{
		UE_LOG(LogTemp, Warning, TEXT("It is empty!"));
		return false;

	}

	if (FFileHelper::SaveArrayToFile(ToBinary, * FullFilePath))
	{
		ToBinary.FlushCache();
		ToBinary.Empty();
		UE_LOG(LogTemp, Warning, TEXT("Saved successfully"));
		return true;
	}

	ToBinary.FlushCache();
	ToBinary.Empty();

	return false;

}


// This function calls for a save.
void UFileTesting::CallFile()
{
	if (SaveLoadTestingM(FilePathG, TToBinary))
	{
		UE_LOG(LogTemp, Warning, TEXT("Call to save worked!"));

	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("Call to save failed!"));

	}
}

///////////////////////////////////////////////////////////////////


// This is the function that loads binary arrays
bool UFileTesting::LLoadTesting(const FString& FullFilePath, int32& SaveDataInt32, FString& SaveStringData)
{

	TArray<uint8> TheBinaryArray;

	if (!FFileHelper::LoadFileToArray(TheBinaryArray, *FilePathG))
	{

		UE_LOG(LogTemp, Warning, TEXT("Load failed at array!"));
		return false;
	}


	
	if (TheBinaryArray.Num() <= 0)
	{ 
		UE_LOG(LogTemp, Warning, TEXT("Failed to load, array is empty!"));

		return false;
	}

	UE_LOG(LogTemp, Warning, TEXT("Loaded file size %d"), TheBinaryArray.Num());


	FMemoryReader FromBinary = FMemoryReader(TheBinaryArray, true);
	FromBinary.Seek(0);

	// This somehow sends the data to SaveLoadTesting, but I don't understand how that eventually saves it.
	SaveLoadTesting(FromBinary, EmptyInt, EmptyString);


	FromBinary.FlushCache();

	TheBinaryArray.Empty();

	return true;
}



void UFileTesting::LoadFile() // This is my call to load things
{


	if (LLoadTesting(FilePathG, EmptyInt, EmptyString))
	{
		UE_LOG(LogTemp, Warning, TEXT("Load worked!"));
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("Load failed!"));
	}


}

From my understanding, I just had a quick look at the FArchive class in ArchiveBase.h and if you have source code access you should probably do so as well, FArchive overloads the << operator for various types which allows you to read or write data of certain types to or from specific archives. So when SaveLoadTesting is called from SaveLoadTestingM with ToBinary as the first parameter, it writes the values of EmptyInt and EmptyString to this archive. This is just the first step, no saving to disk has taken place yet. The actual file is written with FFileHelper::SaveArrayToFile shortly after. You may be wondering that in LLoadTesting (after loading the file from disk using FFileHelper::LoadFileToArray) Rama uses the same function SaveLoadTesting to read the values back into EmptyInt and EmptyString. This works because the first parameter is now FromBinary which reads the values from the TArray<uint8>.

Does that explain your questions?

The << operator works because of functions like this:

[FONT=Courier New]FORCEINLINE FArchive& operator<<(FArchive &Ar, UMySaveGameClass* SaveGameData )
{
if(!SaveGameData) return;
//~

Ar &lt;&lt; SaveGameData-&gt;NumGemsCollected;  //int32
Ar &lt;&lt; SaveGameData-&gt;PlayerLocation;  //FVector
Ar &lt;&lt; SaveGameData-&gt;ArrayOfRotationsOfTheStars; //TArray&lt;FRotator&gt;

}

Which provides the compiler with the operator << to take an instance of [FONT=Courier New]UMySaveGameClass* and write into an [FONT=Courier New]FArchive object. There obviously exists in the code provided to do this for the base type (int32, etc.), although I have not looked for them.

This pattern is standard in the C++ runtime library to read/write data from/to streams (cin/cout: standard in/standard out).

Yeah man, thanks. For some reason I was having a hard time understanding a call like that. I knew it was something simple I didn’t understand so I am glad that I asked here!

That was the exact problem I was having trouble understanding, thanks! For some reason my mind was having a hard time understanding why I’d be doing that with the variables.