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


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

This is nothing to do with it being a plugin. You are simply trying to use an incomplete type - you haven’t included the header file with the definition of UDialogueGraph in DialogueAsset.cpp.

You could add the include, but you’re going to end up with dependency issues. You really need to rethink how you are structuring this so that the runtime module doesn’t have a hard dependency on the editor module. Ideally, your UDialogueAsset class should be able to exist without having any concept of what a UDialogueGraph is. Your editor module, though, can very happily have a dependency on your runtime module and include the definitions for UDialogueAsset. That way your graph and related code should be able to create and configure instances of your asset.

Okay! I ought to be able to initialize stuff like this in the asset factory instead :slight_smile: Thanks for the clarification!
I do still wonder how this works inside the engine, though - I’m working off of sound cues, and those have almost the exact same setup - asset in runtime, referencing graph types in the editor. Got any clues on what’s going on there?

Yeah it looks like they have some editor only properties on the asset that reference back to the asset editor side of things.
Sometimes it’s just necessary to have mutual dependencies like this, sometimes it just happens over time. All I can say is, my advice would be to try as hard as you can to avoid it, and start off with having dependencies only in one direction, with the editor side dependent on the runtime side.

Awesome, thanks. I feel I get tripped up on occasion when I follow the engine code almost to the letter :stuck_out_tongue:

I suggest you look at animation nodes for reference instead. Each animation node type consists of separate editor (derived from UAnimGraphNode_Base) and runtime (derived from FAnimNode_Base) classes, and the assets (UAnimInstance, UAnimMontage, various things derived from UAnimationAsset) are entirely separate classes.