// Copyright Epic Games, Inc. All Rights Reserved. #include "DerivedDataBuildDefinition.h" #include "Algo/AllOf.h" #include "Containers/Map.h" #include "Containers/StringConv.h" #include "Containers/StringView.h" #include "Containers/UnrealString.h" #include "DerivedDataBuildKey.h" #include "DerivedDataBuildPrivate.h" #include "Misc/Guid.h" #include "Misc/TVariant.h" #include "Serialization/CompactBinary.h" #include "Serialization/CompactBinaryWriter.h" #include namespace UE::DerivedData::Private { class FBuildDefinitionBuilderInternal final : public IBuildDefinitionBuilderInternal { public: inline FBuildDefinitionBuilderInternal(FStringView InName, FStringView InFunction) : Name(InName) , Function(InFunction) { checkf(!Name.IsEmpty(), TEXT("A build definition requires a non-empty name.")); AssertValidBuildFunctionName(Function, Name); } ~FBuildDefinitionBuilderInternal() final = default; void AddConstant(FStringView Key, const FCbObject& Value) final { Add(Key, Value); } void AddInputBuild(FStringView Key, const FBuildPayloadKey& PayloadKey) final { Add(Key, PayloadKey); } void AddInputBulkData(FStringView Key, const FGuid& BulkDataId) final { Add(Key, BulkDataId); } void AddInputFile(FStringView Key, FStringView Path) final { Add(Key, Path); } void AddInputHash(FStringView Key, const FIoHash& RawHash) final { Add(Key, RawHash); } FBuildDefinition Build() final; using InputType = TVariant; FString Name; FString Function; TMap Inputs; private: template inline void Add(FStringView Key, ArgType&& Value) { const uint32 KeyHash = GetTypeHash(Key); checkf(!Key.IsEmpty(), TEXT("Empty key used in definition for build of '%s' by %s."), *Name, *Function); checkf(!Inputs.ContainsByHash(KeyHash, Key), TEXT("Duplicate key '%.*s' used in definition ") TEXT("for build of '%s' by %s."), Key.Len(), Key.GetData(), *Name, *Function); Inputs.EmplaceByHash(KeyHash, Key, InputType(TInPlaceType(), Forward(Value))); } }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class FBuildDefinitionInternal final : public IBuildDefinitionInternal { public: explicit FBuildDefinitionInternal(FBuildDefinitionBuilderInternal&& DefinitionBuilder); explicit FBuildDefinitionInternal(FStringView Name, FCbObject&& Definition, bool& bOutIsValid); ~FBuildDefinitionInternal() final = default; const FBuildKey& GetKey() const final { return Key; } FStringView GetName() const final { return Name; } FStringView GetFunction() const final { return Function; } bool HasConstants() const final; bool HasInputs() const final; void IterateConstants(TFunctionRef Visitor) const final; void IterateInputBuilds(TFunctionRef Visitor) const final; void IterateInputBulkData(TFunctionRef Visitor) const final; void IterateInputFiles(TFunctionRef Visitor) const final; void IterateInputHashes(TFunctionRef Visitor) const final; void Save(FCbWriter& Writer) const final; inline void AddRef() const final { ReferenceCount.fetch_add(1, std::memory_order_relaxed); } inline void Release() const final { if (ReferenceCount.fetch_sub(1, std::memory_order_acq_rel) == 1) { delete this; } } private: FString Name; FString Function; FCbObject Definition; FBuildKey Key; mutable std::atomic ReferenceCount{0}; }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// FBuildDefinitionInternal::FBuildDefinitionInternal(FBuildDefinitionBuilderInternal&& DefinitionBuilder) : Name(MoveTemp(DefinitionBuilder.Name)) , Function(MoveTemp(DefinitionBuilder.Function)) { DefinitionBuilder.Inputs.KeySort(TLess<>()); bool bHasConstants = false; bool bHasBuilds = false; bool bHasBulkData = false; bool bHasFiles = false; bool bHasHashes = false; for (const TPair& Pair : DefinitionBuilder.Inputs) { if (Pair.Value.IsType()) { bHasConstants = true; } else if (Pair.Value.IsType()) { bHasBuilds = true; } else if (Pair.Value.IsType()) { bHasBulkData = true; } else if (Pair.Value.IsType()) { bHasFiles = true; } else if (Pair.Value.IsType()) { bHasHashes = true; } } const bool bHasInputs = bHasBuilds | bHasBulkData | bHasFiles | bHasHashes; TCbWriter<2048> Writer; Writer.BeginObject(); Writer.AddString("Function"_ASV, Function); if (bHasConstants) { Writer.BeginObject("Constants"_ASV); for (const TPair& Pair : DefinitionBuilder.Inputs) { if (Pair.Value.IsType()) { Writer.AddObject(FTCHARToUTF8(Pair.Key), Pair.Value.Get()); } } Writer.EndObject(); } if (bHasInputs) { Writer.BeginObject("Inputs"_ASV); } if (bHasBuilds) { Writer.BeginObject("Builds"_ASV); for (const TPair& Pair : DefinitionBuilder.Inputs) { if (Pair.Value.IsType()) { const FBuildPayloadKey& PayloadKey = Pair.Value.Get(); Writer.BeginObject(FTCHARToUTF8(Pair.Key)); Writer.AddHash("Build"_ASV, PayloadKey.BuildKey.Hash); Writer.AddObjectId("Payload"_ASV, PayloadKey.Id); Writer.EndObject(); } } Writer.EndObject(); } if (bHasBulkData) { Writer.BeginObject("BulkData"_ASV); for (const TPair& Pair : DefinitionBuilder.Inputs) { if (Pair.Value.IsType()) { Writer.AddUuid(FTCHARToUTF8(Pair.Key), Pair.Value.Get()); } } Writer.EndObject(); } if (bHasFiles) { Writer.BeginObject("Files"_ASV); for (const TPair& Pair : DefinitionBuilder.Inputs) { if (Pair.Value.IsType()) { Writer.AddString(FTCHARToUTF8(Pair.Key), Pair.Value.Get()); } } Writer.EndObject(); } if (bHasHashes) { Writer.BeginObject("Hashes"_ASV); for (const TPair& Pair : DefinitionBuilder.Inputs) { if (Pair.Value.IsType()) { Writer.AddBinaryAttachment(FTCHARToUTF8(Pair.Key), Pair.Value.Get()); } } Writer.EndObject(); } if (bHasInputs) { Writer.EndObject(); } Writer.EndObject(); Definition = Writer.Save().AsObject(); Key.Hash = Definition.GetHash(); } FBuildDefinitionInternal::FBuildDefinitionInternal(FStringView InName, FCbObject&& InDefinition, bool& bOutIsValid) : Name(InName) , Function(InDefinition.FindView("Function"_ASV).AsString()) , Definition(MoveTemp(InDefinition)) , Key{Definition.GetHash()} { checkf(!Name.IsEmpty(), TEXT("A build definition requires a non-empty name.")); Definition.MakeOwned(); bOutIsValid = Definition && IsValidBuildFunctionName(Function) && Algo::AllOf(Definition.AsView()["Constants"_ASV], [](FCbFieldView Field) { return Field.GetName().Len() > 0 && Field.IsObject(); }) && Algo::AllOf(Definition.AsView()["Inputs"_ASV]["Builds"_ASV], [](FCbFieldView Field) { return Field.GetName().Len() > 0 && Field.IsObject() && Field["Build"_ASV].IsHash() && Field["Payload"_ASV].IsObjectId(); }) && Algo::AllOf(Definition.AsView()["Inputs"_ASV]["BulkData"_ASV], [](FCbFieldView Field) { return Field.GetName().Len() > 0 && Field.IsUuid(); }) && Algo::AllOf(Definition.AsView()["Inputs"_ASV]["Files"_ASV], [](FCbFieldView Field) { return Field.GetName().Len() > 0 && Field.AsString().Len() > 0; }) && Algo::AllOf(Definition.AsView()["Inputs"_ASV]["Hashes"_ASV], [](FCbFieldView Field) { return Field.GetName().Len() > 0 && Field.IsBinaryAttachment(); }); } bool FBuildDefinitionInternal::HasConstants() const { return Definition["Constants"_ASV].HasValue(); } bool FBuildDefinitionInternal::HasInputs() const { return Definition["Inputs"_ASV].HasValue(); } void FBuildDefinitionInternal::IterateConstants(TFunctionRef Visitor) const { for (FCbField Field : Definition["Constants"_ASV]) { Visitor(FUTF8ToTCHAR(Field.GetName()), Field.AsObject()); } } void FBuildDefinitionInternal::IterateInputBuilds(TFunctionRef Visitor) const { for (FCbFieldView Field : Definition.AsView()["Inputs"_ASV]["Builds"_ASV]) { const FBuildKey BuildKey{Field["Build"_ASV].AsHash()}; const FPayloadId Id = Field["Payload"_ASV].AsObjectId(); Visitor(FUTF8ToTCHAR(Field.GetName()), FBuildPayloadKey{BuildKey, Id}); } } void FBuildDefinitionInternal::IterateInputBulkData(TFunctionRef Visitor) const { for (FCbFieldView Field : Definition.AsView()["Inputs"_ASV]["BulkData"_ASV]) { Visitor(FUTF8ToTCHAR(Field.GetName()), Field.AsUuid()); } } void FBuildDefinitionInternal::IterateInputFiles(TFunctionRef Visitor) const { for (FCbFieldView Field : Definition.AsView()["Inputs"_ASV]["Files"_ASV]) { Visitor(FUTF8ToTCHAR(Field.GetName()), FUTF8ToTCHAR(Field.AsString())); } } void FBuildDefinitionInternal::IterateInputHashes(TFunctionRef Visitor) const { for (FCbFieldView Field : Definition.AsView()["Inputs"_ASV]["Hashes"_ASV]) { Visitor(FUTF8ToTCHAR(Field.GetName()), Field.AsBinaryAttachment()); } } void FBuildDefinitionInternal::Save(FCbWriter& Writer) const { Writer.AddObject(Definition); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// FBuildDefinition FBuildDefinitionBuilderInternal::Build() { return CreateBuildDefinition(new FBuildDefinitionInternal(MoveTemp(*this))); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// FBuildDefinition CreateBuildDefinition(IBuildDefinitionInternal* Definition) { return FBuildDefinition(Definition); } FBuildDefinitionBuilder CreateBuildDefinitionBuilder(IBuildDefinitionBuilderInternal* DefinitionBuilder) { return FBuildDefinitionBuilder(DefinitionBuilder); } FBuildDefinitionBuilder CreateBuildDefinition(FStringView Name, FStringView Function) { return CreateBuildDefinitionBuilder(new FBuildDefinitionBuilderInternal(Name, Function)); } } // UE::DerivedData::Private namespace UE::DerivedData { FOptionalBuildDefinition FBuildDefinition::Load(FStringView Name, FCbObject&& Definition) { bool bIsValid = false; FOptionalBuildDefinition Out = Private::CreateBuildDefinition( new Private::FBuildDefinitionInternal(Name, MoveTemp(Definition), bIsValid)); if (!bIsValid) { Out.Reset(); } return Out; } } // UE::DerivedData