Hello folks,
I’ve got a bit of a problem. I will show the code first and then explain below.
MODULE A
ConnTypesRandProperty.h
// Copyright William Pimentel-Tonche. All rights reserved.
#pragma once
#include "CoreMinimal.h"
#include "ConnTypesRandProperty.generated.h"
#define MIN_FLOAT 1.17549e-038
#define MAX_FLOAT 3.40282e+038
#define MIN_INTEGER -2147483647
#define MAX_INTEGER 2147483647
// TODO: Turn this into an Advanced Property, supporting attribute binding and maybe some other stuff alongside randomization. This would make using this as a variable for the raycast component a lot easier.
// TODO: To get around some awkward two-way issues, you may be able to move GameplayStatics to the Core Framework instead.
/**
* Defines rules for randomizing a value.
*/
UENUM( BlueprintType )
enum class EConnRandMode : uint8
{
NoRand UMETA(DisplayName = "Don't Randomize", Tooltip = "Don't randomize this value."),
RandAlways UMETA(DisplayName = "Randomize Always", Tooltip = "Every time we get this value, randomize it."),
RandInterval UMETA(DisplayName = "Randomize at Intervals", Tooltip = "After the specified amounts of times getting this value, randomize it.")
};
/**
* Parameters for generating a random numerical value.
*/
USTRUCT( BlueprintType )
struct CONNDATATYPES_API FConnRandParamsBase
{
GENERATED_BODY()
FConnRandParamsBase();
/**
* Whether to use a pre-determined seed for the random number generator, or to generate a random seed.
*
* You may not need to use this in regular game logic, but you CAN bind this to UI elements.
* For example, the player could adjust this seed in a menu to ensure that a specific world is generated.
*/
UPROPERTY( Category = "RNG", EditAnywhere, BlueprintReadWrite, meta = (DisplayName = "Use Custom Seed (UI Compatible)", DisplayAfter = "MaxValue") )
bool bUseCustomSeed;
/** If selected, this is the pre-determined seed for the random number generator. */
UPROPERTY( Category = "Default", EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "bUseCustomSeed", DisplayAfter = "bUseCustomSeed") )
int32 CustomSeed;
};
/**
* Parameters for generating a random float value.
*/
USTRUCT( BlueprintType )
struct CONNDATATYPES_API FConnRandParamsFloat : public FConnRandParamsBase
{
GENERATED_BODY()
FConnRandParamsFloat();
FConnRandParamsFloat(const float MinimumValue, const float MaximumValue);
FConnRandParamsFloat(const float MinimumValue, const float MaximumValue, const int32 Seed);
/**
* The minimum size of the float that can be generated.
* If same as MaxValue, the floating point number lower limit is used instead (a very small number).
*/
UPROPERTY( Category = "RNG", EditAnywhere, BlueprintReadWrite )
float MinValue;
/**
* The maximum size of the float that can be generated.
* If same as MinValue, the floating point number upper limit is used instead (a very large number).
*/
UPROPERTY( Category = "RNG", EditAnywhere, BlueprintReadWrite )
float MaxValue;
};
/**
* Parameters for generating a random integer value.
*/
USTRUCT( BlueprintType )
struct CONNDATATYPES_API FConnRandParamsInt : public FConnRandParamsBase
{
GENERATED_BODY()
FConnRandParamsInt();
FConnRandParamsInt(const int32 MinimumValue, const int32 MaximumValue);
FConnRandParamsInt(const int32 MinimumValue, const int32 MaximumValue, const int32 Seed);
/**
* The minimum size of the value that can be generated.
* If same as MaxValue, the computer-friendly lower limit is used instead (a very small number).
*/
UPROPERTY(Category = "Default", EditAnywhere, BlueprintReadWrite)
int32 MinValue;
/**
* The maximum size of the value that can be generated.
* If same as MinValue, the computer-friendly upper limit is used instead (a very large number).
*/
UPROPERTY(Category = "Default", EditAnywhere, BlueprintReadWrite)
int32 MaxValue;
};
/**
* Contains params for random interval ranges.
*/
USTRUCT( BlueprintType )
struct CONNDATATYPES_API FConnRandIntervals
{
GENERATED_BODY()
FConnRandIntervals();
/**
* Interval starts at this value.
* Must be at least 0.
*/
UPROPERTY( Category = "RNG", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0") )
int32 IntervalMin;
/**
* Interval ends at this value.
* Set this to Interval Min if you only want one number.
* Set to default for theoretically infinite range.
*/
UPROPERTY( Category = "RNG", EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "0") )
int32 IntervalMax;
/**
* Returns whether or not a value is within the specified range.
*/
bool WithinRange(const int32 InInterval) const;
};
/**
* Base struct for a number that can be randomized.
*/
USTRUCT( BlueprintType )
struct CONNDATATYPES_API FConnNumWithRand
{
GENERATED_BODY()
FConnNumWithRand();
/**
* Randomization options.
* DON'T RANDOMIZE: Doesn't randomize. Good for most values.
* RANDOMIZE ALWAYS: Randomize this value every time we request to get this number. Good for maximum chaos.
* RANDOMIZE AT INTERVAL: Randomize this value on intervals listed below. Better for performance if using the returned value often.
*/
UPROPERTY( Category = "RNG", EditAnywhere, BlueprintReadOnly, meta = (DisplayAfter = "Value") )
EConnRandMode RandomizationMode;
/**
* The intervals at which to accept randomization again.
* Each call to get this number is an interval.
* If you only wanted to randomize once, for example, you would add a value of "1" and no more.
*
* Only applies if "Randomize at Intervals" is set above.
*/
UPROPERTY( Category = "RNG", EditAnywhere, BlueprintReadOnly, meta = (DisplayAfter = "Randomizers") )
TArray<FConnRandIntervals> RandomizationIntervals;
/**
* Stores how many times we've updated ("set") or retrieved ("gotten") this value, random or not.
* Used for RNG purposes, which is why it has to be save-able (and thus not transient).
*/
UPROPERTY( Category = "RNG", VisibleAnywhere, BlueprintReadOnly, SaveGame, meta = (DisplayAfter = "RandomizationIntervals") )
int32 ValueUpdates;
};
/**
* Float value that can be randomized.
*/
USTRUCT( BlueprintType )
struct CONNDATATYPES_API FConnFloatWithRand : public FConnNumWithRand
{
GENERATED_BODY()
FConnFloatWithRand();
// ReSharper disable once CppNonExplicitConvertingConstructor
// Reason: We want this to be non-explicit, so we can set it with = instead without having to type out the whole constructor syntax
FConnFloatWithRand(const float InValue);
FConnFloatWithRand(const float InValue, const bool InitRandomizers);
/**
* Default (and at runtime, current) value.
* Used whenever output is not randomized, but set any time the value is updated.
*/
UPROPERTY( Category = "RNG", EditAnywhere, BlueprintReadWrite )
float Value;
/**
* The array of randomization data sets to apply to this value, IF randomization is enabled.
* If there are multiple randomizers, then the engine takes the mean (average) of all generated numbers and spits out that value.
*
* Note:
* An EXTREMELY excessive amount of randomizers may cause performance issues if these are run every frame.
* Just be careful with how many randomizers you use. You don't need as many as you may think, if you're using this at all.
*/
UPROPERTY( Category = "RNG", EditAnywhere, BlueprintReadWrite, meta = (DisplayAfter = "RandomizationMode") )
TArray<FConnRandParamsFloat> Randomizers;
};
/**
* Integer value that can be randomized.
*/
USTRUCT( BlueprintType )
struct CONNDATATYPES_API FConnIntWithRand : public FConnNumWithRand
{
GENERATED_BODY()
FConnIntWithRand();
// Reason: We want this to be non-explicit, so we can set it with just = instead
// ReSharper disable once CppNonExplicitConvertingConstructor
FConnIntWithRand(const int32 InValue);
/**
* Current value.
* Used whenever output is not randomized.
*/
UPROPERTY( Category = "RNG", EditAnywhere, BlueprintReadWrite, meta = (DisplayAfter = "RandomizationMode") )
int32 Value;
/**
* The array of randomization data sets to apply to this value, IF randomization is enabled.
* If there are multiple randomizers, then the engine takes the mean (average) of all generated numbers and spits out that value.
*
* Note:
* An EXTREMELY excessive amount of randomizers may cause performance issues if these are run every frame.
* Please be careful with how many randomizers you use.
*/
UPROPERTY( Category = "RNG", EditAnywhere, BlueprintReadWrite, meta = (DisplayAfter = "Value") )
TArray<FConnRandParamsInt> Randomizers;
};
MODULE B
ConnAttributeComponent.h
/**
* Methods for updating attributes.
* Automatic updates were created primarily for using attributes as timers.
*/
UENUM( BlueprintType )
enum class EConnAttributeUpdateMethod : uint8
{
Manual UMETA( DisplayName = "Don't Auto-Update (Manual Only)", Tooltip = "Update this value manually. This will likely be the case for the large majority of attributes, unless used as a timer." ),
Main UMETA( DisplayName = "Owning Main Tick (Manual Compatible)", Tooltip = "Update this value with the attribute tick specified in the owning component. This defaults to updating 20 times per second (every 0.05 seconds) but is configurable in the manager." ),
Custom UMETA( DisplayName = "Custom Update (Manual Compatible)", Tooltip = "Update this value by a custom tick of its own." )
};
USTRUCT( BlueprintType )
struct CONNATTRIBUTEFRAMEWORK_API FConnAttributeQuantitative
{
GENERATED_BODY()
FConnAttributeQuantitative();
/**
* Update method of this attribute.
* Booleans are inverted when updated automatically.
* This option CAN be changed at runtime.
*/
UPROPERTY( Category = "Attribute System", EditAnywhere, BlueprintReadOnly, AdvancedDisplay, meta = (DisplayAfter = "Value") )
EConnAttributeUpdateMethod UpdateMethod;
/**
* Custom update interval of this value, if applicable.
* This value CAN be changed at runtime.
*/
UPROPERTY( Category = "Attribute System", EditAnywhere, BlueprintReadOnly, AdvancedDisplay, meta = (EditCondition = "UpdateMethod == EConnAttributeUpdateMethod::Custom", EditConditionHides, DisplayAfter = "UpdateMethod") )
FConnFloatWithRand CustomUpdateInterval;
/**
* This enables/disables having a maximum value which the attribute will be clamped to.
* This switch CANNOT be changed at runtime.
*/
UPROPERTY( Category = "Attribute System", EditAnywhere, BlueprintReadOnly, AdvancedDisplay, meta = (DisplayAfter = "UpdateQuantity") )
bool bEnableMinimumValue;
/**
* This enables/disables having a maximum value which the attribute will be clamped to.
* This switch CANNOT be changed at runtime.
*/
UPROPERTY( Category = "Attribute System", EditAnywhere, BlueprintReadOnly, AdvancedDisplay, meta = (DisplayAfter = "MinValue") )
bool bEnableMaximumValue;
};
USTRUCT( BlueprintType )
struct CONNATTRIBUTEFRAMEWORK_API FConnAttributeQuantitativeInt : public FConnAttributeQuantitative
{
GENERATED_BODY()
FConnAttributeQuantitativeInt();
explicit FConnAttributeQuantitativeInt(const int32 InValue);
FORCEINLINE bool operator==(const FConnAttributeQuantitativeInt& Other) const
{
return Value.Value == Other.Value.Value;
}
/**
* Value of this attribute.
*/
UPROPERTY( Category = "Attributes|Quantitative Attributes", EditAnywhere )
FConnIntWithRand Value;
/**
* Previously set value of this attribute.
* This is visible in the editor to let you know that it exists. (For example, it could be used in UI.)
*/
UPROPERTY( Category = "Attributes|Quantitative Attributes", VisibleAnywhere, BlueprintReadOnly )
int32 PreviousValue;
/**
* If updating automatically, this specifies how much the value should be updated each time.
* This value CAN be changed at runtime.
*/
UPROPERTY( Category = "Attribute System", EditAnywhere, BlueprintReadOnly, AdvancedDisplay, meta = (EditCondition = "UpdateMethod != EConnAttributeUpdateMethod::Manual", EditConditionHides, DisplayAfter = "CustomUpdateInterval") )
FConnIntWithRand UpdateQuantity;
/**
* If enabled, this specifies the minimum value of this attribute.
* This value CAN be changed at runtime.
*/
UPROPERTY( Category = "Attribute System", EditAnywhere, BlueprintReadOnly, AdvancedDisplay, meta = (EditCondition = "bEnableMinimumValue", EditConditionHides, DisplayAfter = "bEnableMinimumValue") )
int32 MinValue;
/**
* If enabled, this specifies the maximum value of this attribute.
* This value CAN be changed at runtime.
*/
UPROPERTY( Category = "Attribute System", EditAnywhere, BlueprintReadOnly, AdvancedDisplay, meta = (EditCondition = "bEnableMaximumValue", EditConditionHides, DisplayAfter = "bEnableMaximumValue") )
int32 MaxValue;
};
USTRUCT( BlueprintType )
struct CONNATTRIBUTEFRAMEWORK_API FConnAttributeQuantitativeFlt : public FConnAttributeQuantitative
{
GENERATED_BODY()
FConnAttributeQuantitativeFlt();
explicit FConnAttributeQuantitativeFlt(const float InValue);
FORCEINLINE bool operator==(const FConnAttributeQuantitativeFlt& Other) const
{
return Value.Value == Other.Value.Value;
}
/**
* Value of this attribute.
*/
UPROPERTY( Category = "Attributes|Quantitative Attributes", EditAnywhere )
FConnFloatWithRand Value;
/**
* Previously set value of this attribute.
* This is visible in the editor to let you know that it exists. (For example, it could be used in UI.)
*/
UPROPERTY( Category = "Attributes|Quantitative Attributes", VisibleAnywhere, BlueprintReadOnly )
float PreviousValue;
/**
* If updating automatically, this specifies how much the value should be updated each time.
* This value CAN be changed at runtime.
*/
UPROPERTY( Category = "Attribute System", EditAnywhere, BlueprintReadOnly, AdvancedDisplay, meta = (EditCondition = "UpdateMethod != EConnAttributeUpdateMethod::Manual", EditConditionHides, DisplayAfter = "CustomUpdateInterval") )
FConnFloatWithRand UpdateQuantity;
/**
* If enabled, this specifies the minimum value of this attribute.
* This value CAN be changed at runtime.
*/
UPROPERTY( Category = "Attribute System", EditAnywhere, BlueprintReadOnly, AdvancedDisplay, meta = (EditCondition = "bEnableMinimumValue", EditConditionHides, DisplayAfter = "bEnableMinimumValue") )
float MinValue;
/**
* If enabled, this specifies the maximum value of this attribute.
* This value CAN be changed at runtime.
*/
UPROPERTY( Category = "Attribute System", EditAnywhere, BlueprintReadOnly, AdvancedDisplay, meta = (EditCondition = "bEnableMaximumValue", EditConditionHides, DisplayAfter = "bEnableMaximumValue") )
float MaxValue;
};
// ...there's other code beyond this point (for the component class itself) but I don't really think it's all too related to the problem, this is more about the structs
So now here’s the dilemma:
I want to be able to add a few fields to FConnFloatWithRand that will allow me to give the framework data it will need at runtime to find an Attribute Component that the value can bind to (as I plan to move these from being RNG-compatible numbers to “Advanced Properties”).
I would do this by adding the parameters below to the base FConnNumWithRand struct class.
UPROPERTY( Category = "Default", EditAnywhere )
FGameplayTag BindAttributeTag;
UPROPERTY( Category = "Default", EditAnywhere )
FName BindComponentTag;
Now this is where the problem begins. I also want to add a field which allows the developer to specify a class, so that if a property on some feature needs a specific attribute from a specific component class to bind to without having to burn through a bunch of tags and hoping that’s enough to set some classes apart. However, because they’re in their own modules and the Module B already depends on Module A, I end up not being able to use the class directly.
// somewhere at the top of the header
class UConnAttributeComponent;
// now back down here
// (to clarfiy, the source I posted above didn't have the forward declaration I just showed you; however, I know I'm gonna have to try this again because I already tried this in the past before scrapping the idea I had at the time; I know I'll have to try it again, and it did exactly what I'm about to describe, so just hang in there with me)
UPROPERTY( Category = "Default", EditAnywhere )
TSubclassOf<UConnAttributeComponent> BindComponentClass; // <== UH OH!
No big deal, I could just forward declare and call it a day right?
Well, I’m stil very new to modules, and in hindsight I have no idea why I ever even thought that would work. It turns out that trying to just forward declare the class and run with it seems to cause the compiler to spit loads of errors.
I probably shouldn’t exactly edit the heirarchy right now because everything’s in its own place, though that could end up being my only solution (I’d hate to have to go back to one single module for the entire framework though). It would seem ridiculous, though, that I’d have to go through all that restructuring all over one single class reference.
Now that you have the background, here’s the main question: Is there a way to somehow reference a class from Module B inside of a struct in Module A, when Module B already depends on Module A as a public dependency?
Pointers:
- Module A is the framework’s generic data types module. That’s the reason why the NumWithRand struct was there - it’s a generic data type. Adding attribute bindings to me doesn’t seem like it would suddenly make it not as generic, especially because it’s still highly necessary for a static raycast library I have.
- Module B is the framework’s Attribute System module, though the classes within (not including the structs, those obviously are giving me problems) don’t really matter to this context as far as I’m aware. This is mostly on the end of the structures themselves.
Worst case scenario is I have to come up with some wild tag syntax standard and pray developers RTFM (I promise I do this myself, and yeah I haven’t seen any solution to this online)… but I would still like to solve this regardless, so if anyone has any advice, I would greatly appreciate it!
And in the case of RTFM, I will have to laugh at myself if it turns out that I could just have modules include each other all along. But I’m pretty sure I read somewhere that such a method won’t work, you’ll just generate more errors than you solve…