Code Monkey home page Code Monkey logo

dataconfig's Introduction

DataConfig

Serialization framework for Unreal Engine that just works!

Unreal Engine features a powerful Property System which implements C++ runtime reflection. DataConfig is a serialization framework build on top of it. Notably features:

  • Out of the box JSON/MsgPack read write.
  • Full support for UPROPERTY()/UCLASS()/USTRUCT()/UENUM().
  • Pull/Push styled API for verbatim data access and lossless type infomation.
  • Designed as a collection of tools that can be easily extended to support other formats.

Documentation

See DataConfig Book for details.

Example

Given a struct annotated and processed with Unreal's Property System:

UENUM()
enum class EDcTestExampleEnum
{
    Foo, Bar, Baz
};

USTRUCT()
struct FDcTestExampleStruct
{
    GENERATED_BODY()
    UPROPERTY() FString StrField;
    UPROPERTY() EDcTestExampleEnum EnumField;
    UPROPERTY() TArray<FColor> Colors;
};

DataConfig can deserialize an instance from JSON with the snippet below:

FString Str = TEXT(R"(
    {
        "StrField" : "Lorem ipsum dolor sit amet",
        "EnumField" : "Bar",
        "Colors" : [
            "#FF0000FF", "#00FF00FF", "#0000FFFF"
        ]
    }
)");

FDcTestExampleStruct Dest;

//  create and setup a deserializer
FDcDeserializer Deserializer;
DcSetupJsonDeserializeHandlers(Deserializer);
Deserializer.AddPredicatedHandler(
    FDcDeserializePredicate::CreateStatic(DcExtra::PredicateIsColorStruct),
    FDcDeserializeDelegate::CreateStatic(DcExtra::HandlerColorDeserialize)
);

//  prepare deserialize context
FDcPropertyDatum Datum(&Dest);
FDcJsonReader Reader(Str);
FDcPropertyWriter Writer(Datum);

FDcDeserializeContext Ctx;
Ctx.Reader = &Reader;
Ctx.Writer = &Writer;
Ctx.Deserializer = &Deserializer;
DC_TRY(Ctx.Prepare());

//  kick off deserialization
DC_TRY(Deserializer.Deserialize(Ctx));

//  validate results
check(Dest.StrField == TEXT("Lorem ipsum dolor sit amet"));
check(Dest.EnumField == EDcTestExampleEnum::Bar);
check(Dest.Colors[0] == FColor::Red);
check(Dest.Colors[1] == FColor::Green);
check(Dest.Colors[2] == FColor::Blue);

Note that enum is deserialized by its name and FColor is deserialized from a html color string like #RRGGBBAA.

Say if we accidentally mistyped the EnumField value:

{
    "StrField" : "Lorem ipsum dolor sit amet",
    "EnumField" : "Far",

DataConfig would fail gracefully with diagnostics:

# DataConfig Error: Enum name not found in enum type: EDcTestExampleEnum, Actual: 'Far'
- [JsonReader] --> <in-memory>4:25
   2 |    {
   3 |        "StrField" : "Lorem ipsum dolor sit amet",
   4 |        "EnumField" : "Far",
     |                           ^
   5 |        "Colors" : [
   6 |            "#FF0000FF", "#00FF00FF", "#0000FFFF"
- [PropertyWriter] Writing property: (FDcTestExampleStruct)$root.(EEDcTestExampleEnum)EnumField

License

DataConfig is released under MIT License.

It would be trememdously helpful if you credit us in your projects. See the license page for details.

dataconfig's People

Contributors

jagt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

dataconfig's Issues

Possible Opt in instead of opt out meta tag?

I know we can add DcSkip to disable being considered for serialize/deserialize but it would be great if instead we could 'flip' the behavior.

UCLASS(DcOptIn)
UPROPERTY(meta = (Dc)) int OptInField1;

I'm sure I could easily switch it to always be opt in here.
https://github.com/slowburn-dev/DataConfig/blob/release/DataConfig/Source/DataConfigCore/Private/DataConfig/Property/DcPropertyTypes.cpp#L13

To just switch the logic there. But it would be great if it could be switched on a uclass or ustruct basis instead. Would require a little more effort.

Also just wanted to say I only recently discovered this plugin and bought the premium plugin. It is also one of the most useful pieces of code I have ever found for unreal and for data. It's an incredible shame that it's not talked about more. Having the text based configs for various assets is something that I think unreal should have integrated earlier on.

Does this work with UObjects & Blueprint actors?

Looking for a way to serialize BP created variables to a simple FString or bytes array that I can store to disk - json sounds nice but its in my case not a requirement (although, a humanly readable format like json would be preferred).

Reorganise source to support DataConfig as drop-in git submodule (suggestion, not an issue)

My typical workflow is to fork a project I'm working on/with, create a new branch for any changes I might make, then use git submodules to place the fork within its containing project.

This allows changes to be pushed to the right repo, and others to clone the project and have all the submodules, with the changes on the new branch, automagically be in place.

With DataConfig, the actual Plugin directory (with the .uplugin file) is not at the top level so this doesn't work by default.

Right now, I need to clone the fork locally, then make a copy of /DataConfig into the plugins directory of my project. This is simple enough, however, if I make changes, I need to manually copy those changes back to the fork.

So... after that long and probably unnecessary introduction, the suggestion I'd like to make is to remove the DataConfig/DataConfig directory and place the contents at the top level.

I don't know how many others would find this helpful, and maybe it's just some latent OCD creeping in, but having to duplicate code out of it's repo gives my project "special needs", and requires users to learn those needs.

PS. Of course this can already be done by moving things around in my branch, but if there's something to contribute back from the fork to the parent project, it makes it a bit trickier. In any case, just close this issue if you don't see value in the suggestion :)

Need help in deserializing MsgPack Data in Unreal Engine 4.26

Hello, I am working on extending the DReyeVR driving simulator by integrating an external open-source eye tracker, name Pupil-Lab Core Glasses. This eye-tracker uses ZeroMQ PUB-SUB and MsgPack to share the data to its subscribers. I am interested in “surface” topic of the eye tracker whose message looks like (see this for details):

{
    "topic": "surfaces.surface_name",
    "name": "surface_name",
    "surf_to_img_trans": (
        (-394.2704714040225, 62.996680859974035, 833.0782341017057),
        (24.939461954010476, 264.1698344383364, 171.09768247735033),
        (-0.0031580300961504023, 0.07378146751738948, 1.0),
    ),
    "img_to_surf_trans": (
        (-0.002552357406770253, 1.5534025217146223e-05, 2.1236555655143734),
        (0.00025853538051076233, 0.003973842600569134, -0.8952954577358644),
        (-2.71355412859636e-05, -0.00029314688183396006, 1.0727627809231568),
    ),
    "gaze_on_surfaces": (
        {
            "topic": "gaze.3d.1._on_surface",
            "norm_pos": (-0.6709809899330139, 0.41052111983299255),
            "confidence": 0.5594810076623645,
            "on_surf": False,
            "base_data": ("gaze.3d.1.", 714040.132285),
            "timestamp": 714040.132285,
        },
        ...,
    ),
    # list of fixations associated with
    "fixations_on_surfaces": (
        {
            "topic": "fixations_on_surface",
            "norm_pos": (-0.9006409049034119, 0.7738968133926392),
            "confidence": 0.8663407531808505,
            "on_surf": False,
            "base_data": ("fixations", 714039.771958),
            "timestamp": 714039.771958,
            "id": 27,
            "duration": 306.62299995310605,  # in milliseconds
            "dispersion": 1.4730711610581475,  # in degrees
        },
        ...,
    ),
    # timestamp of the world video frame in which the surface was detected
    "timestamp": 714040.103912,
}

In my Project, I have implemented two methods, EstablishEyeTrackerConnection() and GetSurfaceData() which are responsible for establishing a connection and retriving the data, as shown below:

bool AEgoVehicle::EstablishEyeTrackerConnection() {
    try {
        UE_LOG(LogTemp, Display, TEXT("ZeroMQ: Attempting to establish connection"));
        //  Prepare our context and subscriber
        Context = new zmq::context_t(1);
        std::string Address = "127.0.0.1";
        std::string RequestPort = "50020";
        zmq::socket_t Requester(*Context, ZMQ_REQ);
        Requester.connect("tcp://" + Address + ":" + RequestPort);
        UE_LOG(LogTemp, Display, TEXT("ZeroMQ: Connected to the TCP port"));

        // Get the SUBSRIBE port to connect for communication
        std::string RequestString = "SUB_PORT";
        zmq::message_t Request(RequestString.begin(), RequestString.end());
        UE_LOG(LogTemp, Display, TEXT("ZeroMQ: Sending request to get SUB PORT"));
        Requester.send(Request);
        zmq::message_t Reply;
        Requester.recv(&Reply);
        UE_LOG(LogTemp, Display, TEXT("ZeroMQ: Received SUB PORT"));
        std::string SubscribePort = std::string(static_cast<char*>(Reply.data()), Reply.size());

        // Setup the Subscriber socket
        Subscriber = new zmq::socket_t(*Context, ZMQ_SUB);
        UE_LOG(LogTemp, Display, TEXT("ZeroMQ: Connecting to the SUB PORT"));
        Subscriber->connect("tcp://" + Address + ":" + SubscribePort);
        UE_LOG(LogTemp, Display, TEXT("ZeroMQ: Connection successful"));
        Subscriber->setsockopt(ZMQ_SUBSCRIBE, "surface", 7);
        UE_LOG(LogTemp, Display, TEXT("ZeroMQ: Subscribed to surface topic"));
    }
    catch (...) {
        // Log a generic error message
        UE_LOG(LogTemp, Error, TEXT("ZeroMQ: Failed to connect to the Pupil labs Network API"));
        return false;
    }
    UE_LOG(LogTemp, Display, TEXT("ZeroMQ: Established connection to the Pupil labs Network API"));
    return true;
}
FDcResult AEgoVehicle::GetSurfaceData() {
    // Note: using raw C++ types in the following code as it does not interact with UE interface
    // Establish connection if not already
    if (Subscriber == nullptr) {
        if (!EstablishEyeTrackerConnection()) {
            UE_LOG(LogTemp, Error, TEXT("ZeroMQ: Failed to connect to the Pupil labs Network API"));
        }
    }

    // Receive an update and update to a string
    zmq::message_t Update;
    if (!Subscriber->recv(&Update)) {
        UE_LOG(LogTemp, Error, TEXT("ZeroMQ: Failed to receive update from subscriber"));
    }
    std::string Topic(static_cast<char*>(Update.data()), Update.size());

    // Receive a message from the server
    zmq::message_t Message;
    if (!Subscriber->recv(&Message)) {
        UE_LOG(LogTemp, Error, TEXT("ZeroMQ: Failed to receive message from subscriber"));
    }

    // Store the serialized data into a TArray
    TArray<uint8> DataArray;
    DataArray.Append(static_cast<uint8*>(Message.data()), Message.size());

    // Create a destination variable and deserializer
    FSurfaceData Destination;
    FDcDeserializer Deserializer;

    // Prepare context for this run
    FDcPropertyDatum Datum(&Destination);
    FDcMsgPackReader Reader(FDcBlobViewData::From(DataArray));
    FDcPropertyWriter Writer(Datum);

    FDcDeserializeContext Ctx;
    Ctx.Reader = &Reader;
    Ctx.Writer = &Writer;
    Ctx.Deserializer = &Deserializer;
    DC_TRY(Ctx.Prepare());

    DC_TRY(Deserializer.Deserialize(Ctx));
    return DcOk();
}

As shown in your example, I have implemented a few USTRUCTS to store the data:

// FFloatArray is a USTRUCT that contains an array of floats. 
// This is used to represent the arrays of floats in the original data structure.
USTRUCT()
struct FFloatArray
{
	GENERATED_BODY()

	UPROPERTY()
	TArray<float> data;
};

// FGazeOnSurface and FFixationsOnSurface are USTRUCTs that represent the individual elements 
// in the gaze_on_surfaces and fixations_on_surfaces arrays, respectively.
USTRUCT()
struct FGazeOnSurface
{
	GENERATED_BODY()

	UPROPERTY()
	FString topic;

	UPROPERTY()
		TArray<float> norm_pos;

	UPROPERTY()
		float confidence;

	UPROPERTY()
		bool on_surf;

	UPROPERTY()
		TArray<FString> base_data;

	UPROPERTY()
		float timestamp;
};

USTRUCT()
struct FFixationsOnSurface
{
	GENERATED_BODY()

	UPROPERTY()
	FString topic;

	UPROPERTY()
		TArray<float> norm_pos;

	UPROPERTY()
		float confidence;

	UPROPERTY()
		bool on_surf;

	UPROPERTY()
		TArray<FString> base_data;

	UPROPERTY()
		float timestamp;

	UPROPERTY()
		float duration;

	UPROPERTY()
		float dispersion;
};


// FSurfaceData is a USTRUCT that contains all the data from the original data structure. 
// It uses the other USTRUCTs to represent the nested arrays.
USTRUCT()
struct FSurfaceData
{
	GENERATED_BODY()

	UPROPERTY()
	FString topic;

	UPROPERTY()
		FString name;

	UPROPERTY()
		TArray<FFloatArray> surf_to_img_trans;

	UPROPERTY()
		TArray<FFloatArray> img_to_surf_trans;

	UPROPERTY()
		TArray<FFloatArray> surf_to_dist_img_trans;

	UPROPERTY()
		TArray<FFloatArray> dist_img_to_surf_trans;

	UPROPERTY()
		TArray<FGazeOnSurface> gaze_on_surfaces;

	UPROPERTY()
		TArray<FFixationsOnSurface> fixations_on_surfaces;

	UPROPERTY()
		float timestamp;
};

The following implementation does not seem to work. Earlier, I tried to use DcEnv().FlushDiags() just below the deserialization call to get the errors, I got the following error in the logs, although I have been unable to replicate this error again.

[2023.07.06-23.44.16:714][140]DataConfig: Error: # DataConfig Error: No matching handler, Property: 'SurfaceData' 'ScriptStruct'
- [DcMsgPackReader] Last read: Int8
- [DcPropertyWriter] Writing property: (FSurfaceData)$root
- [DcSerializer] 
### Properties: (1) :
  - SurfaceData
### Objects: (0).

I am really lost. I have no clue what the problem is. Could you please help me out? I am just a beginner and would greatly appreciate your assistance. I have also attached the full logs from UE if it helps.
CarlaUE4.log

DataConfigCore is not compatible with packaged builds

Hi,

Great library! Thank-you!
I'm attempting to package a project that uses DataConfigCore, but am running into these errors in the packaging step:

DataConfigCore\Private\DataConfig\Automation\DcAutomationUtils.cpp(657): error C2039: 'HasMetaData': is not a member of 'UField'
Engine\Source\Runtime\CoreUObject\Public\UObject\FieldPathProperty.h(19): note: see declaration of 'UField'
DataConfigCore\Private\DataConfig\Automation\DcAutomationUtils.cpp(658): error C2039: 'SetMetaData': is not a member of 'UField'
Engine\Source\Runtime\CoreUObject\Public\UObject\FieldPathProperty.h(19): note: see declaration of 'UField'
DataConfigCore\Private\DataConfig\Automation\DcAutomationUtils.cpp(666): error C2039: 'HasMetaData': is not a member of 'FProperty'
Engine\Source\Runtime\CoreUObject\Public\UObject\PropertyAccessUtil.h(17): note: see declaration of 'FProperty'
DataConfigCore\Private\DataConfig\Automation\DcAutomationUtils.cpp(667): error C2039: 'SetMetaData': is not a member of 'FProperty'
Engine\Source\Runtime\CoreUObject\Public\UObject\PropertyAccessUtil.h(17): note: see declaration of 'FProperty'
DataConfigCore\Private\DataConfig\Deserialize\Handlers\Common\DcCommonDeserializers.cpp(97): error C2039: 'HasMetaData': is not a member of 'UField'
Engine\Source\Runtime\CoreUObject\Public\UObject\FieldPathProperty.h(19): note: see declaration of 'UField'
DataConfigCore\Private\DataConfig\Deserialize\Handlers\MsgPack\DcMsgPackCommonDeserializers.cpp(26): error C2039: 'HasMetaData': is not a member of 'FFieldVariant'
Engine\Source\Runtime\CoreUObject\Public\UObject\Field.h(262): note: see declaration of 'FFieldVariant'
DataConfigCore\Private\DataConfig\ExtraHandlers\DcSerDeInstancedStruct.cpp(57): error C2039: 'HasMetaData': is not a member of 'FField'
Engine\Source\Runtime\CoreUObject\Public\Templates\Casts.h(27): note: see declaration of 'FField'
DataConfigCore\Private\DataConfig\ExtraHandlers\DcSerDeInstancedStruct.cpp(59): error C2039: 'GetMetaData': is not a member of 'FField'
Engine\Source\Runtime\CoreUObject\Public\Templates\Casts.h(27): note: see declaration of 'FField'
DataConfigCore\Private\DataConfig\ExtraHandlers\DcSerDeInstancedStruct.cpp(59): error C2530: 'BaseStructStr': references must be initialized
DataConfigCore\Private\DataConfig\Property\DcPropertyTypes.cpp(13): error C2039: 'HasMetaData': is not a member of 'FProperty'
Engine\Source\Runtime\CoreUObject\Public\UObject\PropertyAccessUtil.h(17): note: see declaration of 'FProperty'
DataConfigCore\Private\DataConfig\Serialize\Handlers\Common\DcCommonSerializers.cpp(96): error C2039: 'HasMetaData': is not a member of 'UField'
Engine\Source\Runtime\CoreUObject\Public\UObject\FieldPathProperty.h(19): note: see declaration of 'UField'
DataConfigCore\Private\DataConfig\Serialize\Handlers\MsgPack\DcMsgPackCommonSerializers.cpp(27): error C2039: 'HasMetaData': is not a member of 'FFieldVariant'
Engine\Source\Runtime\CoreUObject\Public\UObject\Field.h(262): note: see declaration of 'FFieldVariant'

It looks like calls to HasMetaData are not wrapped in WITH_EDITOR defines, so break the build when packaging a game. I see that there is a comment in DcAutomationUtils.cpp:

//	UXXX(meta=(Foo)) only get compiled in `WITH_EDITOR`
//	need to manually amend it on Program targets

Not sure what this means in this context.

Build Errors on MacOS arm64 in source build of UE5.3.2

/Users/jupiter/TestProject/Plugins/DataConfig/Intermediate/Build/Mac/UnrealEditor/Inc/DataConfigCore/UHT/DcTypes.gen.cpp:152:3: error: no matching function for call to 'UEArrayCountHelper'
                UE_ARRAY_COUNT(Z_Construct_UEnum_DataConfigCore_EDcDataEntry_Statics::Enumerators),
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/jupiter/UnrealEngine/UE_5.3.2_Source/Engine/Source/Runtime/Core/Public/Templates/UnrealTemplate.h:197:41: note: expanded from macro 'UE_ARRAY_COUNT'
#define UE_ARRAY_COUNT( array ) (sizeof(UEArrayCountHelper(array)) - 1)
                                        ^~~~~~~~~~~~~~~~~~
/Users/jupiter/UnrealEngine/UE_5.3.2_Source/Engine/Source/Runtime/Core/Public/Templates/UnrealTemplate.h:190:7: note: candidate template ignored: substitution failure [with T = const UECodeGen_Private::FEnumeratorParam[]]: invalid application of 'sizeof' to an incomplete type 'const UECodeGen_Private::FEnumeratorParam[]'
        auto UEArrayCountHelper(T& t) -> typename TEnableIf<__is_array(T), char(&)[sizeof(t) / sizeof(t[0]) + 1]>::Type;
             ^                                                                           ~

There are also "expected unqualified-id" errors wherever Nil appears. eg.

case EDcDataEntry::Nil:
if (Next == EDcDataEntry::Nil)
FDcDataVariant() : DataType(EDcDataEntry::Nil) {}
DataType = EDcDataEntry::Nil;
enum class EDcDataEntry : uint16 { Nil, ...

and many more.

Is there something I've missed?

apart from those 2 issue - it all looks good up to this point :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.