[PLUGIN] Savior

No, I was saying that it’s a problem in PIE that I never found a way to reproduce.

Could be, I am not sure.


Within every Async class’ Activate() body, the value should be probably be reset.
But I didn’t have the time to test:

	USavior3::LastThreadState = USavior3::ThreadSafety;
	USavior3::ThreadSafety = EThreadSafety::IsCurrentlyThreadSafe;

Cool, I’ll test that out tomorrow

Thanks but your answer bring me to a new question: why there’re 3 different savior method to create sguid?

Especially, why I have to use in Game Mode “create once Sguid” to make it work while in the third person character Bp I use Savior make Sguid Actor to make it works?

So far, from my understanding, an actor an instance of a class (basically a pointer) and in game Game mode is an instance too.

So why I have to use 2 different methods for those two objects which are both instances?

This is Unreal Engine’s weirdness.
I have a post that tries to explain a bit about it here:

Understanding SGUID · Savior Wiki · GitHub

Thanks… and congratz for the plug in: a lifesaver in my Rpg open world :slight_smile:

Carefull: most probably a Null pointer Error

Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0xffffffffffffffff

UE4Editor_Savior3!SerializeGameMode() [D:\build++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\Savior\Source\Savior3\Public\Savior3.h:2550]
UE4Editor_Savior3!FAutoDeleteAsyncTask<TASK_SerializeGameMode>::DoWork() [D:\RocketSync\4.26.0-14830424+++UE4+Release-4.26\Working\Engine\Source\Runtime\Core\Public\Async\AsyncWork.h:100]
kernel32

any idea on why it happened?

Can’t know without callstack.
If you have a crashlog, email me please.


Where are you calling “Save Game Mode” node from?

So I have an object that is spawned as a subobject of another object (you can tell by the parents SGUID preceeding the ObjectID) and I can’t ever seem to get it to load properly. The slot data record includes the random number appended to the end of the object name and therefore never matches the slot data key for the record.

I feel like the name of the object should be EEAttributeSet_Resources_C_2147481136 but it doesn’t have the _C_ and is just EEAttributeSet_Resources_2147481136. Since it doesn’t have the _C_ your code is never able to trim just the name and therefore returns the whole thing.

With this being the case, I am never be able to load those objects from a saved file that weren’t spawned during that session (since they will always have different names tied to the slot data).

Also, this class doesn’t have a blueprint class and is C++ object. Perhaps that’s why it doesn’t have the _C_? Have you tried testing something that isn’t a blueprint?

How am I supposed to go about resolving this?

Yes, that’s why.
Unfortunately I am still waiting for free time to check this.
I will try take a look this next weekend.

Crash LOG just sent to your email

Thanks for your time

Hey Bruno, I’m back working on Saviors. I’ve got most of the saving/loading an Actor with dynamic Components working, except for one thing; saving/loading array of structures.

Savior support saving/loading Array of Objects but not Structures.

So I’m trying to add the feature myself. I expect something like this would work but I can’t get it to work.

		const auto OBJs = *Property->ContainerPtrToValuePtr<TArray<FStructProperty*>>(Container);
		for (auto OBJ : OBJs) {
			for (TFieldIterator<FProperty>IT(OBJ->Struct); IT; ++IT) {
				FProperty* Field = *IT; 
				for (int32 I=0; I<Field->ArrayDim; ++I) {
					auto ValuePtr = Field->ContainerPtrToValuePtr<void>(OBJ,I);
					ParseFStructPropertyToJSON(JSON,Field,ValuePtr,Container,0,CPF_Transient,OBJ->IsNative());
				}
			}
			TSharedRef< FJsonValueObject > JValue = MakeShareable( new FJsonValueObject( JSON) );
			JArray.Push(JValue);
		}

The problem is accessing the structure inside of the array and I tried everything I can think of but I can’t access it. It’s should be a simple task but ran out of ideas.

For example,
This is the array I’m trying to save.

    UPROPERTY(BlueprintReadWrite, SaveGame, Category="Decal")
	TArray<FDecalLayerInformation> DecalLayers;
	

And this is the structure in question.


USTRUCT(BlueprintType, Blueprintable)
struct FDecalLayerInformation
{
	GENERATED_USTRUCT_BODY()

public:
	UPROPERTY(EditAnywhere, SaveGame, BlueprintReadWrite)
	float PositionX;
	
	UPROPERTY(EditAnywhere, SaveGame, BlueprintReadWrite)
	float PositionY;
};

Can you please help? Thanks.

@ChrisK I think the built-in Json parser in Unreal does not support that.
I think you have to use an ArrayHelper there to do what you want.

I came across the same problem while writing a C# compiler for Blueprint nodes.
The only way I found to serialize the custom Array of Struct coming from C#, was to iterate through each struct item by myself, I had to go a bit low level into the BPs VM for that;

I will show you what I did, both to write a Vector and what I did to read it back to C#, might give you ideas of what is going on…


  • The Blueprint high level, common nodes:
UFUNCTION(Category="MagicNode", BlueprintCallable, CustomThunk, meta=(WorldContext="Context", BlueprintInternalUseOnly=true))
static void SetMonoArray_Vector3D (UObject* Context, UMagicNodeSharp* Node, const FName Field, const TArray<FVector>& Input)
{
}

UFUNCTION(Category="MagicNode", BlueprintCallable, meta=(WorldContext="Context", BlueprintInternalUseOnly=true))
static void GetMonoArray_Vector3D (UObject* Context, UMagicNodeSharp* Node, const FName Field, TArray<FVector>& Output);

void UMagicNodeSharp::GetMonoArray_Vector3D (UObject* Context, UMagicNodeSharp* Node, const FName Field, TArray<FVector>& Output)
{
	GET_MonoArrayValue_Vector3D(Node,Field,Output);
}

  • Custom BP VM instructions.
    This is useful to get the address of the array property being edited:
DECLARE_FUNCTION(execSetMonoArray_Vector3D)
{

	P_GET_OBJECT(UObject,_Context);
	P_GET_OBJECT(UMagicNodeSharp,_Node);
	P_GET_PROPERTY(FNameProperty,_Field);
	
	Stack.StepCompiledIn<FArrayProperty>(nullptr);
	void* _Address = Stack.MostRecentPropertyAddress;
	FArrayProperty* _Array = CastField<FArrayProperty>(Stack.MostRecentProperty);
	
	if (!_Array) {Stack.bArrayContextFailed=true; return;}
	
	P_FINISH;
	
	P_NATIVE_BEGIN;
	  SET_MonoArrayValue(_Node, _Field, _Array, _Address);
	P_NATIVE_END;

}

  • Then this is the actual serializer, it’s similar to what Savior does…
    (but in the Savior API the Json API is used as a middle-man to make code easier).
    FScriptArrayHelper is the key point of interest:
void IMonoObject::SET_MonoArrayValue(UMagicNodeSharp* Node, const FName &Field, FArrayProperty* Property, void* Address)
{
	if (Field.IsNone()) {LOG::CS_CHAR(ESeverity::Error,TEXT("__SET_MonoArrayValue::  Field is None.")); return;}
	if (Node==nullptr) {LOG::CS_CHAR(ESeverity::Error,TEXT("__SET_MonoArrayValue::  No Context.")); return;}
	
	MonoObject* ManagedObject = Node->GetMonoObject();

	if (ManagedObject==nullptr)
	{
		LOG::CS_CHAR(ESeverity::Error,TEXT("__SET_MonoArrayValue::  Invalid Managed Object.")); return;
	}
	
	if (MonoProperty* MonoProp = Node->GetMonoProperty(Field))
	{
		FMonoArray Array = FMonoArray(Property,Address);
		void* Args[1]; Args[0] = &Array;
		
		if (MonoMethod* SetMethod = mono_property_get_set_method(MonoProp))
		{
			mono_runtime_invoke(SetMethod, ManagedObject, Args, NULL);
		}
	}
}
void IMonoObject::GET_MonoArrayValue_Vector3D(UMagicNodeSharp* Node, const FName &Field, TArray<FVector>&Output)
{
	if (Field.IsNone()) {LOG::CS_CHAR(ESeverity::Error,TEXT("__GET_MonoArrayValue_Vector3D::  Field is None.")); return;}
	if (Node==nullptr) {LOG::CS_CHAR(ESeverity::Error,TEXT("__GET_MonoArrayValue_Vector3D::  No Context.")); return;}
	
	MonoObject* ManagedObject = Node->GetMonoObject();

	if (ManagedObject==nullptr)
	{
		LOG::CS_CHAR(ESeverity::Error,TEXT("__GET_MonoArrayValue_Vector3D::  Invalid Managed Object.")); return;
	}
	
	if (MonoProperty*MonoProp=Node->GetMonoProperty(Field))
	{
		MonoMethod* GET = mono_property_get_get_method(MonoProp);
		
		if (MonoObject* iCall = mono_runtime_invoke(GET,ManagedObject,NULL,NULL))
		{
			FMonoArray Out = *(FMonoArray*)mono_object_unbox(iCall);
			
			if (FArrayProperty* Property = reinterpret_cast<FArrayProperty*>(Out.Property))
			{
				if (FStructProperty* InnerValue = CastField<FStructProperty>(Property->Inner))
				{
					if (InnerValue->Struct == TBaseStructure<FVector>::Get())
					{
						FScriptArrayHelper Array(Property,Out.ValueAddr);
						Output.SetNum(Array.Num());
						
						for (int32 I=0; I<Array.Num(); ++I)
						{
							const uint8* ValuePtr = Array.GetRawPtr(I);

							FVector ValueCopy;
							
							InnerValue->CopySingleValue(&ValueCopy,ValuePtr);
							Output[I] = ValueCopy;
						}
					}
				}
			}
		}
	}
}

I would do 99% the same thing to bypass problems caused by the Json parser.
But instead of invoking mono_xx API calls there, I would simply create Json object for the array manually, fill it with member values, then inject it into the save record’s “JSON” master object.

@Lupoluke
You get a crash because somehow your SK_Chest skeletal mesh has zero size:

Error: Nans found on Bounds for Primitive SK_Chest : Origin X=nan Y=nan Z=nan, BoxExtent X=nan Y=nan Z=nan, SphereRadius nan

I would not mark this object as “Save Game” property and see what is going on that makes the mesh shrink to zero scale… Your crash happens even before the plugin does anything.

Thanks, that’s an interesting solution.
It’s really complicated to access the Structure within Array, unlike Objects.
I’m not sure why it won’t allow us as Structure itself is also UObject.

Anyway, I have a question looking at the sample code.

I need to create a Structure object in order to copy the value.
In your case, it’s FVector, but my Structure is not known.
Do you know how the object can be instantiated?

Many thanks.

Thanks a bunch.

From what you posted, instead of FVector there, you would use FDecalLayerInformation type as “ValueCopy” into the output array index.


If you mean you want to check the base type,
( InnerValue->Struct == TBaseStructure::Get() )

You can compare struct by type name:

const FString PropType = InnerValue->Struct->GetCPPType();

if ( PropType.Equals(TEXT("FDecalLayerInformation")) )
{
    ///....
}

FDecalLayerInformation is defined in Game project and Savior doesn’t have access to it, unfortunately.

If I just make a simple FDecalLayerInformation Property (not TArray), it saves fine, thus I assume Unreal knows about the structure through the reflections system.

If there is no other way, I’ll try to move the structure definition to Savior plugin.

It’s possible to forward declare types in plugin modules and define them in game module.

Hmm… it’s a structure type that needs be instanced. It can’t be instanced without knowing its size. Perhaps there is Unreal way that I don’t know?

I found that there is something called Field and you can get it through ,
Property->GetInnerFields().

TArray<FField*> fields;
Property->GetInnerFields(fields);
FField field = field(0);
UScriptStruct* decal = ((FStructProperty*)field)->Struct;

It seems to hold some information about the Structure definition and I’m not sure how to use it to get the Structure values stored in the Array Property.

If we find a way, we can make it to work for any Structures without forward declaration. And we may add this general functionality back to Plugin.

Hey @BrUnO_XaVIeR, I’d like to request another change. I’m trying to build a subsystem to test Savior and would love to have a class that gets notified when any SerializableInterface function is executed for an actor. For instance, whenever you call OnPrepareToSave, OnSaved, OnPrepareToLoad, OnLoaded, you also broadcast a delegate on your Savior GameInstanceSubsystem. That way I can encapsulate my logic without requiring each actor that implements the SerializableInterface to forward those calls to my subsystem.

I have started work on V4.
If you can send me an email with details about the changes you want, it’s best way to request changes or new functions.


Btw… About v4:

Now that the old V2 is out of conflict range, I will no longer attach version numbers to files and classes ( Savior3.h will be back to just Savior.h and the classes as well have no more suffix);

I have type redirectors implemented, so your blueprints will not break when the numbering is removed.

The SaviorRecord type will also change on V4, because I need it to change to stop the problems some of you have with MakeActorGUID() functions.
I will no longer add to records the name of recorded object, also the SGUID will be pulled out of Record and used as TMap Key (no longer FNames).

I don’t know when this will be ready tho.
I am working for Japanese studio and the work there is my priority, I am only working on this before bed or weekends.