Incomplete type with forward declared plugin editor module class in runtime module

Hey everyone!

I’m working on a plugin, but I’m still a tad wet behind my ears when it comes to the build system and modules beyond the game module.

At the moment, I’m attempting to add a new asset class with its own custom graph editor - sort of like a sound cue, but for dialogue trees instead. However, I’ve ran into some troubles getting it to compile. A bunch of stuff in the plugin is taken directly from SoundCue.h and SoundCue.cpp, along with its associated graphs and schemas.

On to my issue - I have two modules, one editor and one runtime. The runtime module has, at the moment, only the asset itself while the editor module contains the asset factory, type actions, graph, schema and nodes. What I’ve done so far is have a UDialogueGraph* variable wrapped in WITH_EDITOR #ifdefs and some related functions, and I have forward declared the UDialogueGraph class in the header. However, when I compile, the following error occurs:

In file included from /home//Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Source/DialoguePluginRuntime/Private/DialogueAsset.cpp:1:
In file included from /home//Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Intermediate/Build/Linux/x86_64-unknown-linux-gnu/UE4Editor/Development/DialoguePluginRuntime/DialoguePluginRuntimePrivatePCH.h:4:
In file included from /home//Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Source/DialoguePluginRuntime/Private/DialoguePluginRuntimePrivatePCH.h:4:
In file included from /home//Programming/UnrealEngine/Engine/Source/Runtime/Engine/Public/Engine.h:10:
In file included from /home//Programming/UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/CoreUObject.h:12:
In file included from /home//Programming/UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectBase.h:1563:
/home//Programming/UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectBaseUtility.h:330:14: error: incomplete type 'UDialogueGraph' named in nested name specifier
                return IsA(T::StaticClass());
                           ^~~
/home//Programming/UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/Templates/Casts.h:112:22: note: in instantiation of function template specialization 'UObjectBaseUtility::IsA<UDialogueGraph>' requested here
                return Src && Src->IsA<To>() ? (To*)Src : nullptr;
                                   ^
/home//Programming/UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/Templates/Casts.h:176:30: note: in instantiation of member function 'TCastImpl<UEdGraph, UDialogueGraph, 0>::DoCast' requested here
        return TCastImpl<From, To>::DoCast(Src);
                                    ^
/home//Programming/UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/Templates/Casts.h:192:17: note: in instantiation of function template specialization 'Cast<UDialogueGraph, UEdGraph>' requested here
                        To* Result = Cast<To>(Src);
                                     ^
/home//Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Source/DialoguePluginRuntime/Private/DialogueAsset.cpp:17:12: note: in instantiation of function template specialization 'CastChecked<UDialogueGraph, UEdGraph>' requested here
    return CastChecked<UDialogueGraph>(DialogueGraph);
           ^
../../../../Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Source/DialoguePluginRuntime/Public/DialogueAsset.h:46:37: note: forward declaration of 'UDialogueGraph'
    DIALOGUEPLUGINRUNTIME_API class UDialogueGraph* GetGraph();
                                    ^
In file included from /home//Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Source/DialoguePluginRuntime/Private/DialogueAsset.cpp:1:
In file included from /home//Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Intermediate/Build/Linux/x86_64-unknown-linux-gnu/UE4Editor/Development/DialoguePluginRuntime/DialoguePluginRuntimePrivatePCH.h:4:
In file included from /home//Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Source/DialoguePluginRuntime/Private/DialoguePluginRuntimePrivatePCH.h:4:
In file included from /home//Programming/UnrealEngine/Engine/Source/Runtime/Engine/Public/Engine.h:10:
In file included from /home//Programming/UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/CoreUObject.h:12:
In file included from /home//Programming/UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectBase.h:1563:
/home//Programming/UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/UObject/UObjectBaseUtility.h:330:15: error: incomplete definition of type 'UDialogueGraph'
                return IsA(T::StaticClass());
                           ~^~
In file included from /home//Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Source/DialoguePluginRuntime/Private/DialogueAsset.cpp:1:
In file included from /home//Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Intermediate/Build/Linux/x86_64-unknown-linux-gnu/UE4Editor/Development/DialoguePluginRuntime/DialoguePluginRuntimePrivatePCH.h:4:
In file included from /home//Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Source/DialoguePluginRuntime/Private/DialoguePluginRuntimePrivatePCH.h:4:
In file included from /home//Programming/UnrealEngine/Engine/Source/Runtime/Engine/Public/Engine.h:10:
In file included from /home//Programming/UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/CoreUObject.h:18:
/home//Programming/UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/Templates/Casts.h:53:9: error: incomplete type 'UDialogueGraph' named in nested name specifier
        return T::StaticClass()->GetName();
               ^~~
/home//Programming/UnrealEngine/Engine/Source/Runtime/CoreUObject/Public/Templates/Casts.h:195:55: note: in instantiation of function template specialization 'GetTypeName<UDialogueGraph>' requested here
                                CastLogError(*Cast<UObject>(Src)->GetFullName(), *GetTypeName<To>());
                                                                                  ^
/home//Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Source/DialoguePluginRuntime/Private/DialogueAsset.cpp:17:12: note: in instantiation of function template specialization 'CastChecked<UDialogueGraph, UEdGraph>' requested here
    return CastChecked<UDialogueGraph>(DialogueGraph);
           ^
../../../../Documents/Unreal Projects/DialogueGraphProject/Plugins/DialoguePlugin/Source/DialoguePluginRuntime/Public/DialogueAsset.h:46:37: note: forward declaration of 'UDialogueGraph'
    DIALOGUEPLUGINRUNTIME_API class UDialogueGraph* GetGraph();
                                    ^

This happens when I attempt to cast to the graph type and return it via a function on the asset (also wrapped in WITH_EDITOR) - line 1 in UDialogueAsset::GetGraph(). Here are the relevant classes:

DialogueAsset.h (Runtime module, public)

#pragma once

#include "Engine.h"
#include "DialogueAsset.generated.h"


USTRUCT()
struct FDialogueNodeEditorData
{
    GENERATED_USTRUCT_BODY()

    int32 NodePosX;
    int32 NodePosY;

    FDialogueNodeEditorData()
                : NodePosX (0)
                , NodePosY (0)
    {

    }

    // Overload the << operator to allow archiving of the struct properly.
    friend FArchive& operator<<(FArchive& Ar, FDialogueNodeEditorData& MyDialogueNodeEditorData)
    {
        return Ar << MyDialogueNodeEditorData.NodePosX << MyDialogueNodeEditorData.NodePosY;
    }
};


UCLASS(BlueprintType)
class UDialogueAsset : public UObject
{
    GENERATED_BODY()
public:
    UDialogueAsset();

#if WITH_EDITORONLY_DATA
    // Variables
    class UEdGraph* DialogueGraph;
#endif

#if WITH_EDITOR
    // Functions
    DIALOGUEPLUGINRUNTIME_API void CreateGraph();
    DIALOGUEPLUGINRUNTIME_API void ClearGraph();
    DIALOGUEPLUGINRUNTIME_API class UDialogueGraph* GetGraph();
#endif
};

DialogueAsset.cpp (Runtime module, private)

#include "Private/DialoguePluginRuntimePrivatePCH.h"
#include "Public/DialogueAsset.h"

#if WITH_EDITOR
#include "Kismet2/BlueprintEditorUtils.h"
#include "UnrealEd.h"
#endif //WITH_EDITOR

UDialogueAsset::UDialogueAsset()
{
    
}

#if WITH_EDITOR
UDialogueGraph* UDialogueAsset::GetGraph()
{
    return CastChecked<UDialogueGraph>(DialogueGraph);
}

void UDialogueAsset::CreateGraph()
{
    //TODO: incomplete type named in nested name specifier
    /*
    if (DialogueGraph == nullptr)
    {
        DialogueGraph = CastChecked<UDialogueGraph>(FBlueprintEditorUtils::CreateNewGraph(this, NAME_None, UDialogueGraph::StaticClass(), UDialogueGraphSchema::StaticClass()));
        DialogueGraph->bAllowDeletion = false;

        // Give the schema a chance to fill out any required nodes
        const UEdGraphSchema* Schema = DialogueGraph->GetSchema();
        Schema->CreateDefaultNodesForGraph(*DialogueGraph);
    }
    */
}

void UDialogueAsset::ClearGraph()
{
    if (DialogueGraph)
    {
        DialogueGraph->Nodes.Empty();

        // Give the schema a chance to fill out any required nodes
        const UEdGraphSchema* Schema = DialogueGraph->GetSchema();
        Schema->CreateDefaultNodesForGraph(*DialogueGraph);
    }
}
#endif //WITH_EDITOR

DialoguePlugin.Build.cs (Editor module)

// Some copyright should be here...

using UnrealBuildTool;

public class DialoguePlugin : ModuleRules
{
	public DialoguePlugin(TargetInfo Target)
	{
		
		PublicIncludePaths.AddRange(
			new string[] {
				"DialoguePlugin/Public"
				
				// ... add public include paths required here ...
			}
			);
				
		
		PrivateIncludePaths.AddRange(
			new string[] {
				"DialoguePlugin/Private",
				
				// ... add other private include paths required here ...
			}
			);
			
		
		PublicDependencyModuleNames.AddRange(
			new string[]
			{
				"Core",				
				// ... add other public dependencies that you statically link with here ...								
			}
			);
			
		
		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
                "CoreUObject", 
                "Engine", 
                "Slate", 
                "SlateCore", 
                "UnrealEd", 
                "GraphEditor",
                "ContentBrowser",
                "DialoguePluginRuntime"
				// ... add private dependencies that you statically link with here ...	
			}
			);						
		
		PrivateIncludePathModuleNames.AddRange(
		    new string[]
		    {
		        "AssetTools"
		    }		
		    );
		
		DynamicallyLoadedModuleNames.AddRange(
			new string[]
			{
				"AssetTools"
				// ... add any modules that your module loads dynamically here ...
			}
			);
	}
}

DialoguePluginRuntime.Build.cs (Runtime module)

// Some copyright should be here...

using UnrealBuildTool;

public class DialoguePluginRuntime : ModuleRules
{
	public DialoguePluginRuntime(TargetInfo Target)
	{
		
		PublicIncludePaths.AddRange(
			new string[] 
			{
				"DialoguePluginRuntime/Public",
				
				// ... add public include paths required here ...
			}
			);
				
		
		PrivateIncludePaths.AddRange(
			new string[] 
			{
				"DialoguePluginRuntime/Private",
				
				// ... add other private include paths required here ...
			}
			);
			
		
		PublicDependencyModuleNames.AddRange(
			new string[]
			{
				"Core",			
				// ... add other public dependencies that you statically link with here ...			
			}
			);
			
		
		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
                "CoreUObject", "Engine", "Slate", "SlateCore"
				// ... add private dependencies that you statically link with here ...	
			}
			);	
		
		DynamicallyLoadedModuleNames.AddRange(
			new string[]
			{
				
				// ... add any modules that your module loads dynamically here ...
			}
			);
	}
}

Plugins are strange! Any help would be greatly appreciated :slight_smile:

Can you please add the .Build.cs code?

Sure thing! Added.

Try this:

 public class DialoguePluginRuntime : ModuleRules
 {
     public DialoguePluginRuntime(TargetInfo Target)
     {
         
         PublicIncludePaths.AddRange(
             new string[] 
             {
                 "DialoguePluginRuntime/Public",
                 
                 // ... add public include paths required here ...
             }
             );
                 
         
         PrivateIncludePaths.AddRange(
             new string[] 
             {
                 "DialoguePluginRuntime/Private",
                 
                 // ... add other private include paths required here ...
             }
             );
             
             
         
         PrivateDependencyModuleNames.AddRange(
             new string[]
             {
                 "CoreUObject", "Engine", "Slate", "SlateCore","UnrealEd","GraphEditor",
                 // ... add private dependencies that you statically link with here ...    
             }
             );    
         
         DynamicallyLoadedModuleNames.AddRange(
             new string[]
             {
                 
                 // ... add any modules that your module loads dynamically here ...
             }
             );
        CircularlyReferencedDependentModules.AddRange(
            new string[] {
                "UnrealEd",
                "GraphEditor",
            });
     }
 }

Thanks for the answer - unfortunately, this did not work :frowning:

Solved! All I needed to do was to rethink my structure a bit.