diff --git a/CHANGELOG.md b/CHANGELOG.md index d74ed71820..24f33aa473 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased-`x.y.z`] - 2019-xx-xx - Added logging for queued RPCs. - Added several new STAT annotations into the ServerReplicateActors call chain. +- Avoid generating schema for all UObject subclasses. Actor, ActorComponent, GameplayAbility subclasses are enabled by default, other classes can be enabled using SpatialType UCLASS specifier. +- Added new experimental CookAndGenerateSchemaCommandlet that generates required schema during a regular cook. ### Features: - Visual Studio 2019 is now supported. diff --git a/RequireSetup b/RequireSetup index 08f31e4633..f04e25d0fb 100644 --- a/RequireSetup +++ b/RequireSetup @@ -1,4 +1,4 @@ Increment the below number whenever it is required to run Setup.bat as part of a new commit. Our git hooks will detect this file has been updated and automatically run Setup.bat on pull. -32 +33 diff --git a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp index 10aa58c818..e2e1189025 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/EngineClasses/SpatialNetDriver.cpp @@ -998,7 +998,7 @@ void USpatialNetDriver::ServerReplicateActors_ProcessPrioritizedActors(UNetConne // or it's an editor placed actor and the client hasn't initialized the level it's in if (Channel == nullptr && GuidCache->SupportsObject(Actor->GetClass()) && GuidCache->SupportsObject(Actor->IsNetStartupActor() ? Actor : Actor->GetArchetype())) { - if (Actor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_NotSpatialType)) + if (!Actor->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType)) { // Trying to replicate an actor that isn't supported by Spatial (e.g. marked NotSpatial) continue; @@ -1301,7 +1301,7 @@ void USpatialNetDriver::ProcessRemoteFunction( // The RPC might have been called by an actor directly, or by a subobject on that actor UObject* CallingObject = SubObject ? SubObject : Actor; - if (CallingObject->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_NotSpatialType)) + if (!CallingObject->GetClass()->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType)) { UE_LOG(LogSpatialOSNetDriver, Verbose, TEXT("Trying to call RPC %s on object %s (class %s) that isn't supported by Spatial. This RPC will be dropped."), *Function->GetName(), *CallingObject->GetName(), *CallingObject->GetClass()->GetName()); return; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp index 46e86d6727..df69ad4d1c 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Interop/SpatialClassInfoManager.cpp @@ -100,7 +100,7 @@ void USpatialClassInfoManager::CreateClassInfoForClass(UClass* Class) // Note: we have to add Class to ClassInfoMap before quitting, as it is expected to be in there by GetOrCreateClassInfoByClass. Therefore the quitting logic cannot be moved higher up. if (!IsSupportedClass(ClassPath)) { - UE_LOG(LogSpatialClassInfoManager, Error, TEXT("Could not find class %s in schema database. Double-check whether replication is enabled for this class, the class is explicitly referenced from the starting scene and schema has been generated."), *ClassPath); + UE_LOG(LogSpatialClassInfoManager, Error, TEXT("Could not find class %s in schema database. Double-check whether replication is enabled for this class, the class is marked as SpatialType, and schema has been generated."), *ClassPath); UE_LOG(LogSpatialClassInfoManager, Error, TEXT("Disconnecting due to no generated schema for %s."), *ClassPath); QuitGame(); return; diff --git a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp index 12402c05bf..6d174570b8 100644 --- a/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp +++ b/SpatialGDK/Source/SpatialGDK/Private/Utils/InterestFactory.cpp @@ -35,7 +35,11 @@ void GatherClientInterestDistances() TMap DiscoveredInterestDistancesSquared; for (TObjectIterator It; It; ++It) { - if (It->HasAnySpatialClassFlags(SPATIALCLASS_ServerOnly | SPATIALCLASS_NotSpatialType)) + if (It->HasAnySpatialClassFlags(SPATIALCLASS_ServerOnly)) + { + continue; + } + if (!It->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType)) { continue; } diff --git a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h index 96ec5ceb4b..93e0528b5f 100644 --- a/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h +++ b/SpatialGDK/Source/SpatialGDK/Public/Utils/EngineVersionCheck.h @@ -7,7 +7,7 @@ // GDK Version to be updated with SPATIAL_ENGINE_VERSION // when breaking changes are made to the engine that requires // changes to the GDK to remain compatible -#define SPATIAL_GDK_VERSION 6 +#define SPATIAL_GDK_VERSION 7 // Check if GDK is compatible with the current version of Unreal Engine // SPATIAL_ENGINE_VERSION is incremented in engine when breaking changes diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp index 5191ce9450..93befdf609 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SchemaGenerator.cpp @@ -12,6 +12,9 @@ #include "Utils/CodeWriter.h" #include "Utils/ComponentIdGenerator.h" #include "Utils/DataTypeUtilities.h" +#include "SpatialGDKEditorSchemaGenerator.h" + +using namespace SpatialGDKEditor::Schema; DEFINE_LOG_CATEGORY(LogSchemaGenerator); @@ -565,7 +568,7 @@ void GenerateSubobjectSchemaForActorIncludes(FCodeWriter& Writer, TSharedPtrObject; - if (Value != nullptr && !Value->IsEditorOnly()) + if (Value != nullptr && IsSupportedClass(Value->GetClass())) { UClass* Class = Value->GetClass(); if (!AlreadyImported.Contains(Class) && SchemaGeneratedClasses.Contains(Class)) diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp index 38236782c2..11e1b05374 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/SpatialGDKEditorSchemaGenerator.cpp @@ -57,7 +57,9 @@ const FString SchemaDatabasePackagePath = FPaths::Combine(FPaths::ProjectContent const FString SchemaDatabaseAssetPath = FPaths::SetExtension(SpatialConstants::SCHEMA_DATABASE_ASSET_PATH, TEXT(".SchemaDatabase")); const FString SchemaDatabaseFileName = FPaths::SetExtension(SchemaDatabasePackagePath, FPackageName::GetAssetPackageExtension()); -namespace +namespace SpatialGDKEditor +{ +namespace Schema { void AddPotentialNameCollision(const FString& DesiredSchemaName, const FString& ClassPath, const FString& GeneratedSchemaName) @@ -240,8 +242,6 @@ bool ValidateIdentifierNames(TArray>& TypeInfos) return bSuccess; } -}// :: - void GenerateSchemaFromClasses(const TArray>& TypeInfos, const FString& CombinedSchemaPath, FComponentIdGenerator& IdGenerator) { // Generate the actual schema. @@ -410,47 +410,78 @@ bool SaveSchemaDatabase() return true; } -TArray GetAllSupportedClasses() +bool IsSupportedClass(const UClass* SupportedClass) { - TSet Classes; + if (!IsValid(SupportedClass)) + { + UE_LOG(LogSpatialGDKSchemaGenerator, Verbose, TEXT("[%s] Invalid Class not supported for schema gen."), *GetPathNameSafe(SupportedClass)); + return false; + } + + if (SupportedClass->IsEditorOnly()) + { + UE_LOG(LogSpatialGDKSchemaGenerator, Verbose, TEXT("[%s] Editor-only Class not supported for schema gen."), *GetPathNameSafe(SupportedClass)); + return false; + } + + if (!SupportedClass->HasAnySpatialClassFlags(SPATIALCLASS_SpatialType)) + { + UE_LOG(LogSpatialGDKSchemaGenerator, Verbose, TEXT("[%s] No SpatialType flag, not supported for schema gen."), *GetPathNameSafe(SupportedClass)); + return false; + } + + if (SupportedClass->HasAnyClassFlags(CLASS_LayoutChanging)) + { + UE_LOG(LogSpatialGDKSchemaGenerator, Verbose, TEXT("[%s] Layout changing, not supported"), *GetPathNameSafe(SupportedClass)); + return false; + } + + // Ensure we don't process transient generated classes for BP + if (SupportedClass->GetName().StartsWith(TEXT("SKEL_"), ESearchCase::CaseSensitive) + || SupportedClass->GetName().StartsWith(TEXT("REINST_"), ESearchCase::CaseSensitive) + || SupportedClass->GetName().StartsWith(TEXT("TRASHCLASS_"), ESearchCase::CaseSensitive) + || SupportedClass->GetName().StartsWith(TEXT("HOTRELOADED_"), ESearchCase::CaseSensitive) + || SupportedClass->GetName().StartsWith(TEXT("PROTO_BP_"), ESearchCase::CaseSensitive) + || SupportedClass->GetName().StartsWith(TEXT("PLACEHOLDER-CLASS_"), ESearchCase::CaseSensitive) + || SupportedClass->GetName().StartsWith(TEXT("ORPHANED_DATA_ONLY_"), ESearchCase::CaseSensitive)) + { + UE_LOG(LogSpatialGDKSchemaGenerator, Verbose, TEXT("[%s] Transient Class not supported for schema gen"), *GetPathNameSafe(SupportedClass)); + return false; + } + const TArray& DirectoriesToNeverCook = GetDefault()->DirectoriesToNeverCook; - for (TObjectIterator ClassIt; ClassIt; ++ClassIt) + // Avoid processing classes contained in Directories to Never Cook + const FString& ClassPath = SupportedClass->GetPathName(); + if (DirectoriesToNeverCook.ContainsByPredicate([&ClassPath](const FDirectoryPath& Directory) { - // User told us to ignore this class - if (ClassIt->HasAnySpatialClassFlags(SPATIALCLASS_NotSpatialType)) - { - continue; - } + return ClassPath.StartsWith(Directory.Path); + })) + { + UE_LOG(LogSpatialGDKSchemaGenerator, Verbose, TEXT("[%s] Inside Directory to never cook for schema gen"), *GetPathNameSafe(SupportedClass)); + return false; + } - UClass* SupportedClass = *ClassIt; + UE_LOG(LogSpatialGDKSchemaGenerator, Verbose, TEXT("[%s] Supported Class"), *GetPathNameSafe(SupportedClass)); + return true; +} - // Ensure we don't process transient generated classes for BP - if (SupportedClass->GetName().StartsWith(TEXT("SKEL_"), ESearchCase::CaseSensitive) - || SupportedClass->GetName().StartsWith(TEXT("REINST_"), ESearchCase::CaseSensitive) - || SupportedClass->GetName().StartsWith(TEXT("TRASHCLASS_"), ESearchCase::CaseSensitive) - || SupportedClass->GetName().StartsWith(TEXT("HOTRELOADED_"), ESearchCase::CaseSensitive) - || SupportedClass->GetName().StartsWith(TEXT("PROTO_BP_"), ESearchCase::CaseSensitive) - || SupportedClass->GetName().StartsWith(TEXT("PLACEHOLDER-CLASS_"), ESearchCase::CaseSensitive) - || SupportedClass->GetName().StartsWith(TEXT("ORPHANED_DATA_ONLY_"), ESearchCase::CaseSensitive)) - { - continue; - } - // Avoid processing classes contained in Directories to Never Cook - const FString& ClassPath = SupportedClass->GetPathName(); - if (DirectoriesToNeverCook.ContainsByPredicate([&ClassPath](const FDirectoryPath& Directory) - { - return ClassPath.StartsWith(Directory.Path); - })) +TSet GetAllSupportedClasses() +{ + TSet Classes; + + for (TObjectIterator ClassIt; ClassIt; ++ClassIt) + { + UClass* SupportedClass = *ClassIt; + + if (IsSupportedClass(SupportedClass)) { - continue; + Classes.Add(SupportedClass); } - - Classes.Add(SupportedClass); } - return Classes.Array(); + return Classes; } void CopyWellKnownSchemaFiles() @@ -560,7 +591,7 @@ bool TryLoadExistingSchemaDatabase() return true; } -SPATIALGDKEDITOR_API bool GeneratedSchemaFolderExists() +bool GeneratedSchemaFolderExists() { const FString SchemaOutputPath = GetDefault()->GetGeneratedSchemaOutputFolder(); IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); @@ -686,19 +717,55 @@ bool RunSchemaCompiler() bool SpatialGDKGenerateSchema() { - ResetUsedNames(); + // Generate Schema for classes loaded in memory. + + if (!SpatialGDKGenerateSchemaForClasses(GetAllSupportedClasses())) + { + return false; + } + + if (!SaveSchemaDatabase()) + { + return false; + } - // Gets the classes currently loaded into memory. - SchemaGeneratedClasses = GetAllSupportedClasses(); - SchemaGeneratedClasses.Sort(); + return RunSchemaCompiler(); +} +bool SpatialGDKGenerateSchemaForClasses(TSet Classes) +{ + ResetUsedNames(); + Classes.Sort([](const UClass& A, const UClass& B) + { + return A.GetPathName() < B.GetPathName(); + }); + // Generate Type Info structs for all classes TArray> TypeInfos; - for (const auto& Class : SchemaGeneratedClasses) + for (const auto& Class : Classes) { + if (SchemaGeneratedClasses.Contains(Class)) + { + continue; + } + + SchemaGeneratedClasses.Add(Class); // Parent and static array index start at 0 for checksum calculations. - TypeInfos.Add(CreateUnrealTypeInfo(Class, 0, 0)); + TSharedPtr TypeInfo = CreateUnrealTypeInfo(Class, 0, 0); + TypeInfos.Add(TypeInfo); + VisitAllObjects(TypeInfo, [&](TSharedPtr TypeNode) + { + if (UClass* NestedClass = Cast(TypeNode->Type)) + { + if (!SchemaGeneratedClasses.Contains(NestedClass) && IsSupportedClass(NestedClass)) + { + TypeInfos.Add(CreateUnrealTypeInfo(NestedClass, 0, 0)); + SchemaGeneratedClasses.Add(NestedClass); + } + } + return true; + }); } if (!ValidateIdentifierNames(TypeInfos)) @@ -724,12 +791,11 @@ bool SpatialGDKGenerateSchema() GenerateSchemaFromClasses(TypeInfos, SchemaOutputPath, IdGenerator); GenerateSchemaForSublevels(SchemaOutputPath, IdGenerator); NextAvailableComponentId = IdGenerator.Peek(); - if (!SaveSchemaDatabase()) - { - return false; - } - return RunSchemaCompiler(); + return true; } +} // Schema +} // SpatialGDKEditor + #undef LOCTEXT_NAMESPACE diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp index 6911f6d5ea..3c9e3bedf3 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.cpp @@ -7,11 +7,7 @@ #include "SpatialGDKEditorSchemaGenerator.h" #include "Utils/RepLayoutUtils.h" -namespace Errors -{ - FString DuplicateComponentError = TEXT("WARNING: Unreal GDK does not currently support multiple static components of the same type.\n" - "Make sure {0} has only one instance of {1} or don't generate type bindings for {2}"); -} +using namespace SpatialGDKEditor::Schema; TArray GetAllReplicatedPropertyGroups() { @@ -24,6 +20,19 @@ FString GetReplicatedPropertyGroupName(EReplicatedPropertyGroup Group) return Group == REP_SingleClient ? TEXT("OwnerOnly") : TEXT(""); } +void VisitAllObjects(TSharedPtr TypeNode, TFunction)> Visitor) +{ + bool bShouldRecurseFurther = Visitor(TypeNode); + for (auto& PropertyPair : TypeNode->Properties) + { + if (bShouldRecurseFurther && PropertyPair.Value->Type.IsValid()) + { + // Recurse into subobjects. + VisitAllObjects(PropertyPair.Value->Type, Visitor); + } + } +} + void VisitAllProperties(TSharedPtr TypeNode, TFunction)> Visitor) { for (auto& PropertyPair : TypeNode->Properties) @@ -465,7 +474,7 @@ FSubobjectMap GetAllSubobjects(TSharedPtr TypeInfo) { UObject* Value = PropertyTypeInfo->Object; - if (Value != nullptr && !Value->IsEditorOnly()) + if (Value != nullptr && IsSupportedClass(Value->GetClass())) { if (!SeenComponents.Contains(Value)) { diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.h b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.h index cba2ff495a..231fa57524 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/TypeStructure.h @@ -123,6 +123,11 @@ TArray GetAllReplicatedPropertyGroups(); // Convert a replicated property group to a string. Used to generate component names. FString GetReplicatedPropertyGroupName(EReplicatedPropertyGroup Group); +// Given an AST, this applies the function 'Visitor' to all FUnrealType's contained transitively within the properties. bRecurseIntoObjects will control +// whether this function will recurse into a UObject's properties, which may not always be desirable. However, it will always recurse into substructs. +// If the Visitor function returns false, it will not recurse any further into that part of the tree. +void VisitAllObjects(TSharedPtr TypeNode, TFunction)> Visitor); + // Given an AST, this applies the function 'Visitor' to all properties contained transitively within the type. This will recurse into substructs. // If the Visitor function returns false, it will not recurse any further into that part of the tree. void VisitAllProperties(TSharedPtr TypeNode, TFunction)> Visitor); diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/CodeWriter.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/CodeWriter.cpp index 9255eaa8e2..b9db6f5563 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/CodeWriter.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SchemaGenerator/Utils/CodeWriter.cpp @@ -120,4 +120,4 @@ void FCodeWriter::WriteToFile(const FString& Filename) void FCodeWriter::Dump() { UE_LOG(LogTemp, Warning, TEXT("%s"), *OutputSource); -} \ No newline at end of file +} diff --git a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp index 9447b15c83..637e90c57a 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp +++ b/SpatialGDK/Source/SpatialGDKEditor/Private/SpatialGDKEditor.cpp @@ -18,6 +18,8 @@ #include "UObject/StrongObjectPtr.h" #include "Settings/ProjectPackagingSettings.h" +using namespace SpatialGDKEditor; + DEFINE_LOG_CATEGORY(LogSpatialGDKEditor); #define LOCTEXT_NAMESPACE "FSpatialGDKEditor" @@ -59,7 +61,7 @@ bool FSpatialGDKEditor::GenerateSchema(bool bFullScan) RemoveEditorAssetLoadedCallback(); - if (!TryLoadExistingSchemaDatabase()) + if (!Schema::TryLoadExistingSchemaDatabase()) { bSchemaGeneratorRunning = false; return false; @@ -89,12 +91,12 @@ bool FSpatialGDKEditor::GenerateSchema(bool bFullScan) if (bFullScan) { // UNR-1610 - This copy is a workaround to enable schema_compiler usage until FPL is ready. Without this prepare_for_run checks crash local launch and cloud upload. - CopyWellKnownSchemaFiles(); - DeleteGeneratedSchemaFiles(); + Schema::CopyWellKnownSchemaFiles(); + Schema::DeleteGeneratedSchemaFiles(); } Progress.EnterProgressFrame(bFullScan ? 10.f : 100.f); - bool bResult = SpatialGDKGenerateSchema(); + bool bResult = Schema::SpatialGDKGenerateSchema(); // We delay printing this error until after the schema spam to make it have a higher chance of being noticed. if (ErroredBlueprints.Num() > 0) @@ -244,7 +246,7 @@ void FSpatialGDKEditor::StopCloudDeployment(FSimpleDelegate SuccessCallback, FSi bool FSpatialGDKEditor::FullScanRequired() { - return !GeneratedSchemaFolderExists() || !GeneratedSchemaDatabaseExists(); + return !Schema::GeneratedSchemaFolderExists() || !Schema::GeneratedSchemaDatabaseExists(); } void FSpatialGDKEditor::RemoveEditorAssetLoadedCallback() diff --git a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h index 51c4bc57bb..c9435225fc 100644 --- a/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h +++ b/SpatialGDK/Source/SpatialGDKEditor/Public/SpatialGDKEditorSchemaGenerator.h @@ -6,18 +6,34 @@ DECLARE_LOG_CATEGORY_EXTERN(LogSpatialGDKSchemaGenerator, Log, All); -SPATIALGDKEDITOR_API bool SpatialGDKGenerateSchema(); - -SPATIALGDKEDITOR_API void ClearGeneratedSchema(); - -SPATIALGDKEDITOR_API void DeleteGeneratedSchemaFiles(); - -SPATIALGDKEDITOR_API void CopyWellKnownSchemaFiles(); - -SPATIALGDKEDITOR_API bool TryLoadExistingSchemaDatabase(); - -SPATIALGDKEDITOR_API bool GeneratedSchemaFolderExists(); - -SPATIALGDKEDITOR_API bool DeleteSchemaDatabase(); - -SPATIALGDKEDITOR_API bool GeneratedSchemaDatabaseExists(); +namespace SpatialGDKEditor +{ + namespace Schema + { + SPATIALGDKEDITOR_API bool IsSupportedClass(const UClass* SupportedClass); + + SPATIALGDKEDITOR_API TSet GetAllSupportedClasses(); + + SPATIALGDKEDITOR_API bool SpatialGDKGenerateSchema(); + + SPATIALGDKEDITOR_API bool SpatialGDKGenerateSchemaForClasses(TSet Classes); + + SPATIALGDKEDITOR_API bool TryLoadExistingSchemaDatabase(); + + SPATIALGDKEDITOR_API bool GeneratedSchemaDatabaseExists(); + + SPATIALGDKEDITOR_API bool SaveSchemaDatabase(); + + SPATIALGDKEDITOR_API bool DeleteSchemaDatabase(); + + SPATIALGDKEDITOR_API void ClearGeneratedSchema(); + + SPATIALGDKEDITOR_API bool GeneratedSchemaFolderExists(); + + SPATIALGDKEDITOR_API void DeleteGeneratedSchemaFiles(); + + SPATIALGDKEDITOR_API void CopyWellKnownSchemaFiles(); + + SPATIALGDKEDITOR_API bool RunSchemaCompiler(); + } +} diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp new file mode 100644 index 0000000000..8d57c90ead --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.cpp @@ -0,0 +1,134 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "CookAndGenerateSchemaCommandlet.h" +#include "SpatialGDKEditorCommandletPrivate.h" +#include "SpatialGDKEditorSchemaGenerator.h" + +using namespace SpatialGDKEditor::Schema; + +DEFINE_LOG_CATEGORY(LogCookAndGenerateSchemaCommandlet); + +struct FObjectListener : public FUObjectArray::FUObjectCreateListener +{ +public: + void StartListening(TSet* ClassesFound) + { + VisitedClasses = ClassesFound; + GUObjectArray.AddUObjectCreateListener(this); + } + + void StopListening() + { + GUObjectArray.RemoveUObjectCreateListener(this); + } + + virtual void NotifyUObjectCreated(const UObjectBase* Object, int32 Index) override + { + FSoftClassPath SoftClass = FSoftClassPath(Object->GetClass()); + if (UnsupportedClasses.Contains(SoftClass)) + { + return; + } + if (!VisitedClasses->Contains(SoftClass)) + { + if (IsSupportedClass(Object->GetClass())) + { + UE_LOG(LogCookAndGenerateSchemaCommandlet, Verbose, TEXT("Object [%s] Created, Consider Class [%s] For Schema."), + *Object->GetFName().ToString(), *GetPathNameSafe(Object->GetClass())); + VisitedClasses->Add(SoftClass); + } + else + { + UnsupportedClasses.Add(SoftClass); + } + } + } + +private: + TSet* VisitedClasses; + TSet UnsupportedClasses; +}; + +UCookAndGenerateSchemaCommandlet::UCookAndGenerateSchemaCommandlet() +{ + IsClient = false; + IsEditor = true; + IsServer = false; + LogToConsole = true; +} + +int32 UCookAndGenerateSchemaCommandlet::Main(const FString& CmdLineParams) +{ + UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Cook and Generate Schema Started.")); + FObjectListener ObjectListener; + TSet ReferencedClasses; + ObjectListener.StartListening(&ReferencedClasses); + + UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Try Load Schema Database.")); + if (!TryLoadExistingSchemaDatabase()) + { + UE_LOG(LogCookAndGenerateSchemaCommandlet, Error, TEXT("Failed to load Schema Database.")); + return 0; + } + + UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Finding supported C++ and in-memory Classes.")); + for (const auto& SupportedClass : GetAllSupportedClasses()) + { + ReferencedClasses.Add(FSoftClassPath(SupportedClass)); + } + + UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Starting Cook Command.")); + int32 CookResult = Super::Main(CmdLineParams); + UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Cook Command Completed.")); + + UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Discovered %d Classes during cook."), ReferencedClasses.Num()); + + ObjectListener.StopListening(); + + // Sort classes here so that batching does not have an effect on ordering. + ReferencedClasses.Sort([](const FSoftClassPath& A, const FSoftClassPath& B) + { + return A.GetAssetPathName() < B.GetAssetPathName(); + }); + + UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Start Schema Generation for discovered assets.")); + FDateTime StartTime = FDateTime::Now(); + TSet Classes; + const int BatchSize = 100; + for (const FSoftClassPath& SoftPath : ReferencedClasses) + { + if (UClass* LoadedClass = SoftPath.TryLoadClass()) + { + UE_LOG(LogCookAndGenerateSchemaCommandlet, Verbose, TEXT("Reloaded %s, adding to batch"), *GetPathNameSafe(LoadedClass)); + Classes.Add(LoadedClass); + if (Classes.Num() >= BatchSize) + { + SpatialGDKGenerateSchemaForClasses(Classes); + Classes.Empty(BatchSize); + } + } + else + { + UE_LOG(LogCookAndGenerateSchemaCommandlet, Warning, TEXT("Failed to reload %s"), *SoftPath.ToString()); + } + } + SpatialGDKGenerateSchemaForClasses(Classes); + + FTimespan Duration = FDateTime::Now() - StartTime; + + UE_LOG(LogCookAndGenerateSchemaCommandlet, Display, TEXT("Schema Generation Finished in %.2f seconds"), Duration.GetTotalSeconds()); + + if (!SaveSchemaDatabase()) + { + UE_LOG(LogCookAndGenerateSchemaCommandlet, Error, TEXT("Failed to save schema database.")); + return 0; + } + + if (!RunSchemaCompiler()) + { + UE_LOG(LogCookAndGenerateSchemaCommandlet, Error, TEXT("Failed to run schema compiler.")); + return 0; + } + + return CookResult; +} diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.h b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.h new file mode 100644 index 0000000000..172a0f567f --- /dev/null +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/CookAndGenerateSchemaCommandlet.h @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Commandlets/CookCommandlet.h" +#include "CookAndGenerateSchemaCommandlet.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogCookAndGenerateSchemaCommandlet, Log, All); + +struct FObjectListener; +/** + * This Commandlet generates schema and performs a cook command. + * It supports the same set of arguments as cook. It will only generate + * schema for blueprints required by the cook. + */ +UCLASS() +class SPATIALGDKEDITORCOMMANDLET_API UCookAndGenerateSchemaCommandlet : public UCookCommandlet +{ + GENERATED_BODY() + + UCookAndGenerateSchemaCommandlet(); + +public: + virtual int32 Main(const FString& CmdLineParams) override; +}; diff --git a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaCommandlet.cpp b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaCommandlet.cpp index f83e2395c2..80cfb42a4c 100644 --- a/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaCommandlet.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorCommandlet/Private/Commandlets/GenerateSchemaCommandlet.cpp @@ -5,6 +5,8 @@ #include "SpatialGDKEditorCommandletPrivate.h" #include "SpatialGDKEditorSchemaGenerator.h" +using namespace SpatialGDKEditor::Schema; + UGenerateSchemaCommandlet::UGenerateSchemaCommandlet() { IsClient = false; diff --git a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp index 5a51237731..01ccd152b8 100644 --- a/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp +++ b/SpatialGDK/Source/SpatialGDKEditorToolbar/Private/SpatialGDKEditorToolbar.cpp @@ -294,7 +294,7 @@ void FSpatialGDKEditorToolbarModule::DeleteSchemaDatabaseButtonClicked() if (FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("DeleteSchemaDatabasePrompt", "Are you sure you want to delete the schema database?")) == EAppReturnType::Yes) { OnShowTaskStartNotification(TEXT("Deleting schema database")); - if (DeleteSchemaDatabase()) + if (SpatialGDKEditor::Schema::DeleteSchemaDatabase()) { OnShowSuccessNotification(TEXT("Schema database deleted")); } @@ -856,7 +856,7 @@ bool FSpatialGDKEditorToolbarModule::IsSchemaGenerated() const { FString DescriptorPath = FSpatialGDKServicesModule::GetSpatialOSDirectory(TEXT("build/assembly/schema/schema.descriptor")); FString GdkFolderPath = FSpatialGDKServicesModule::GetSpatialOSDirectory(TEXT("schema/unreal/gdk")); - return FPaths::FileExists(DescriptorPath) && FPaths::DirectoryExists(GdkFolderPath) && GeneratedSchemaDatabaseExists(); + return FPaths::FileExists(DescriptorPath) && FPaths::DirectoryExists(GdkFolderPath) && SpatialGDKEditor::Schema::GeneratedSchemaDatabaseExists(); } #undef LOCTEXT_NAMESPACE diff --git a/ci/unreal-engine.version b/ci/unreal-engine.version index f02b3c9d63..125d25f7b0 100644 --- a/ci/unreal-engine.version +++ b/ci/unreal-engine.version @@ -1 +1 @@ -UnrealEngine-fb4c7f3f31a05a79085474e3a9d589a69aa720ef \ No newline at end of file +UnrealEngine-2a5bd615a4211185a9a6386d66082521e96ccbb0 \ No newline at end of file